SwiftUI: How to remove delay from dismissing a sheet? - swift

I want to offset a view when a sheet comes up and set it back when the sheet gets dismissed. The code itself works, but I have a problem.
When the sheet first comes up, everything is running as expected. But when I dismiss the sheet, there is a ~ 1 second delay before the offset goes back to 0. (Running it in the actual simulator shows the same result)
How can I get rid of the delay or control the delay duration?
My code:
import SwiftUI
struct FirstView: View {
#State var showSheet: Bool = false
var body: some View {
Text("Hello, World!")
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
LaggyView()
.presentationDetents([.medium, .large])
}
.offset(y: showSheet ? -300 : 0)
.animation(.default, value: showSheet)
}
}
struct LaggyView: View {
var body: some View {
Text("hello")
}
}
struct FirstView_Previews: PreviewProvider {
static var previews: some View {
FirstView()
}
}

Related

SwiftUI: sheet will not present with Picker appeared

It may be a bug of SwiftUI?
There is a Picker component and two buttons in the ContentView. The first button is simply changing the "show" var to true and the second button change the var with a delay. The sheet should be present by tapping the buttons. But I found that if the Picker is tapped at first and keep nothing selected then only the button with delay can present the sheet. The button without delay will cause an error:
[Presentation] Attempt to present <_TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView_: 0x14e80e400> on <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x14c819600> (from <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x14c819600>) which is already presenting <_UIContextMenuActionsOnlyViewController: 0x14d63be90>.
After that, the sheet will never be able to present again. Is that the only solution (adding a delay) to avoid this situation?
import SwiftUI
struct ContentView: View {
#State var show = false
#State var picked = "1"
let values = ["1", "2", "3"]
var body: some View {
VStack {
Picker(selection: $picked, label: Text("")) {
ForEach (values, id: \.self) { value in
Text(value)
}
}
Text("⬆️ Tap me but don't select")
.padding(.bottom, 120)
Text("1. Tap Picker")
Text("2. Tap show sheet button")
Button("show sheet") {
show = false
show = true
}
.padding([.top, .bottom], 20)
Button("show sheet with delay") {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
show = true
}
}
}
.padding()
.sheet(isPresented: $show, content: {
Button("close") {
show = false
}
})
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Show DisclosureGroup view based on state

I'm fairly new to SwiftUI, and I'm having trouble wrapping my head around the following issue I ran into. I have a button which toggles a state property, and I'd like to display a DisclosureGroup when the button's state is toggle on. For some reason, I can display any sort of view with my code below, with the exception of a DisclosureGroup:
#Binding var showing : Bool
#Binding var revealDetails : Bool
var body: some View {
if showing {
VStack {
DisclosureGroup("Monday", isExpanded: $revealDetails){
Text("7PM - 10PM").frame(height: 100)
}
.frame(width: 150)
.buttonStyle(PlainButtonStyle()).accentColor(.black)
}
}
}
}
The above code does not work when I present in my ContentView, however, the strange thing is, if I add some sort of empty view above the DisclosureGroup, it does work. So for now, I'm including a Text("") inside the VStack. Any thoughts on why this is?
I think you're not passing correct values to your bindings, i can tell you clearly after seeing your code in ContentView as you haven't attached it in the question but you can copy paste below code and customise it depending on your needs.
ContentView
import SwiftUI
struct ContentView: View {
// MARK: - PROPERTIES
#State private var showDiscloureGroup = false
#State private var showDetails = false
// MARK: - BODY
var body: some View {
VStack{
Toggle("Show Disclosure Group", isOn: $showDiscloureGroup)
Toggle("Show Details", isOn: $showDetails)
MyDiscloureGroup(showing: $showDiscloureGroup, revealDetails: $showDetails)
}//: VSTACK
.padding()
}
}
// MARK: - PREVIEW
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
MyDiscloureGroupView
import SwiftUI
struct MyDiscloureGroupView: View {
#Binding var showing : Bool
#Binding var revealDetails : Bool
var body: some View {
if showing {
VStack {
DisclosureGroup("Monday", isExpanded: $revealDetails){
Text("7PM - 10PM").frame(height: 100)
}
.frame(width: 150)
.buttonStyle(PlainButtonStyle()).accentColor(.black)
}
}
}
}
struct MyDiscloureGroup_Previews: PreviewProvider {
static var previews: some View {
MyDiscloureGroupView(showing: .constant(true), revealDetails: .constant(true))
}
}

SwiftUI - How to create a transition animation between views without impacting child view?

In SwiftUI, I've got an overarching view setup like this:
import SwiftUI
struct ContentView: View {
#State var index: Int = 0
var body: some View {
if self.index == 0{
FirstView(index: $index)
}
if self.index == 1 {
SecondView(index: $index)
.transition(.move(edge: .bottom))
.animation(.easeIn)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Where I have a button in FirstView that is equivalent of:
Button(action: {
self.index = 1
}){
Text("Change view")
}
This works fine and all is good, but I notice that, with this method, any views conditional on an if/else statement in the child views with a transition animation (i.e. SecondView in this case), also have these animations applied.
So, for example, if SecondView was as follows:
struct SecondView: View {
#Binding var index: Int
#State var boolean: Bool = false
var body: some View {
VStack{
if self.boolean == true {
Text("Hello!")
}
Button(action: {
self.boolean = true
}){
Text("change boolean")
}
}
}
}
the Text("Hello") would also have the .transition(.move(edge: .bottom)) transition.
Is there any way to prevent this/a better way to create a transition animation from one view to another?

Embedded navigationlinks

I am trying to develop an app to do a little cognitive testing
When opening the app there is a NavigationView{} with NavigationLink{}
So far so normal
One of the links
NavigationLink(destination: IntermediateCoreView()) { Text("Go to tests") }
Takes you to a 'prescreen for the tests, from which you can link to the 'test' its self
From the IntermediateCoreView.swift you can NavigationLink(destination: ContentView()) { Text("+ new test") }
Which works, you can take the test. At the end of the test (X seconds pass) and then it displays an alert that the user has run out of time and takes them back to the Intermediate CoreView
This is where it goes wrong as the IntermediateCoreView, 'Back' button in the NavigationView goes back to the 'test' view. Not back to the InitialView
I get that this is 'expected behaviour', the test is back from the screen its been sent to. Is there a way to override this?
To add a minimal example - the .app file:
import SwiftUI
#main
struct minRep2App: App {
var body: some Scene {
WindowGroup {
initialView()
}
}
}
Then the initial view controller:
import SwiftUI
struct initialView: View {
var body: some View {
NavigationView{
List{
NavigationLink(destination: ContentView()) {
Text("Go to CoRe tests")
}
}
}
}
}
struct initialView_Previews: PreviewProvider {
static var previews: some View {
initialView()
}
}
Lastly the test demo
import SwiftUI
struct ContentView: View {
#State private var timeRemaining = 50.00
#State private var showingAlert = false
#State var showIntermediate: Bool = false
let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
var body: some View {
NavigationLink(destination: initialView(), isActive: self.$showIntermediate)
{
EmptyView()
}
Text("Test goes here")
.padding()
HStack{
// Text("Score: \(score)")
Text("Time: \(timeRemaining)")
}.padding(.bottom, 10)
.onReceive(timer) { time in
if self.timeRemaining > 0.1 {
self.timeRemaining -= 1
}
if self.timeRemaining == 0.0 {
self.showingAlert = true
self.timer.upstream.connect().cancel()
}
}
.alert(isPresented: $showingAlert){
Alert(title: Text("Warning"), message: Text("Sorry your time is up!"), dismissButton: .default(Text("OK"), action: {
self.showIntermediate = true
})
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Change your ContentView to this:
import SwiftUI
struct ContentView: View {
#Environment(\.presentationMode) var presentationMode
#State private var timeRemaining = 50.00
#State private var showingAlert = false
let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
var body: some View {
Text("Test goes here")
.padding()
HStack{
// Text("Score: \(score)")
Text("Time: \(timeRemaining)")
}.padding(.bottom, 10)
.onReceive(timer) { time in
if self.timeRemaining > 0.1 {
self.timeRemaining -= 1
}
if self.timeRemaining == 0.0 {
self.showingAlert = true
self.timer.upstream.connect().cancel()
}
}
.alert(isPresented: $showingAlert){
Alert(title: Text("Warning"), message: Text("Sorry your time is up!"), dismissButton: .default(Text("OK"), action: {
self.presentationMode.wrappedValue.dismiss()
})
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This has nothing to do with the back button which will work fine. The problem is the logic in your alert caused you to instantiate a new, wholly different, InitialView. Think of it like webpages. If you navigate from one to the next, and then click on a link that goes to the first one, you have actually gone FORWARD to the first page, and if you click the back button you will get to the second page. Same thing here.
Instead, you should use #Environment(\.presentationMode) var presentationMode which, when you call self.presentationMode.wrappedValue.dismiss() will trigger you to go back to the view that presented the view that you are trying to navigate back from.

How to resolve "Use of unresolved identifier 'PresentationLink'" error in swiftUI? [duplicate]

I am attempting to dismiss a modal view presented via a .sheet in SwiftUI - called by a Button which is within a NavigationViews navigationBarItems, as per below:
struct ModalView : View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button(action: {
self.presentationMode.value.dismiss()
}, label: { Text("Save")})
}
}
struct ContentView : View {
#State var showModal: Bool = false
var body: some View {
NavigationView {
Text("test")
.navigationBarTitle(Text("Navigation Title Text"))
.navigationBarItems(trailing:
Button(action: {
self.showModal = true
}, label: { Text("Add") })
.sheet(isPresented: $showModal, content: { ModalView() })
)
}
}
}
The modal does not dismiss when the Save button is tapped, it just remains on screen. The only way to get rid of it is swiping down on the modal.
Printing the value of self.presentationMode.value always shows false so it seems to think that it hasn't been presented.
This only happens when it is presented from the NavigationView. Take that out and it works fine.
Am I missing something here, or is this a beta issue?
You need to move the .sheet outside the Button.
NavigationView {
Text("test")
.navigationBarTitle(Text("Navigation Title Text"))
.navigationBarItems(trailing:
Button("Add") {
self.showModal = true
}
)
.sheet(isPresented: $showModal, content: { ModalView() })
}
You can even move it outside the NavigationView closure.
NavigationView {
Text("test")
.navigationBarTitle(Text("Navigation Title Text"))
.navigationBarItems(trailing:
Button("Add") { self.showModal = true }
)
}
.sheet(isPresented: $showModal, content: { ModalView() })
Notice you can also simplify the Button call if you have a simple text button.
The solution is not readily apparent in the documentation and most tutorials opt for simple solutions. But I really wanted a button in the NavigationBar of the sheet that would dismiss the sheet. Here is the solution in six steps:
Set the DetailView to not show.
Add a button to set the DetailView to show.
Call the .sheet(isPresented modifier to display the sheet.
Wrap the view that will appear in the sheet in a NavigationView because we want to display a .navigationBarItem button.
PresentationMode is required to dismiss the sheet view.
Add a button to the NavBar and call the dismiss method.
import SwiftUI
struct ContentView: View {
// 1
#State private var showingDetail = false
var body: some View {
VStack {
Text("Hello, world!")
.padding()
Button("Show Detail") {
showingDetail = true // 2
}
// 3
.sheet(isPresented: $showingDetail) {
// 4
NavigationView {
DetailView()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct DetailView: View {
// 5
#Environment(\.presentationMode) var presentationMode
var body: some View {
Text("Detail View!")
// 6
.navigationBarItems(leading: Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "x.circle")
.font(.headline)
.foregroundColor(.accentColor)
})
}
}