Different hide transition than show transition for a view
Here is fix
if isPanelVisible {
VStack {
Button(action: {
withAnimation {
self.isPanelVisible = false
}
}) {
Text("HIDE")
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.white)
.zIndex(1) // << here !!
.transition(.slide)
}
What is the UIButton Navigation push equivalent in SwiftUI
You need to use a NavigationView
but you may hide it.
Try the following:
struct ContentView: View {
@State private var isLinkActive = false
var body: some View {
NavigationView {
VStack {
Button("Go to...") {
// activate link programmatically
self.isLinkActive = true
}
}
// hide navigation bar
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
.background(
NavigationLink(destination: SecondView(), isActive: $isLinkActive) {
EmptyView()
}
.hidden()
)
}
}
}
struct SecondView: View {
@Environment(\.presentationMode) private var presentationMode
var body: some View {
Button("Go back") {
self.presentationMode.wrappedValue.dismiss()
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
}
}
Segue to a new view in SwftUI after a successful authentication from API without a NavigationButton
Assuming you've got two basic views (e.g., a LoginView
and a MainView
), you can transition between them in a couple ways. What you'll need is:
- Some sort of state that determines which is being shown
- Some wrapping view that will transition between two layouts when #1 changes
- Some way of communicating data between the views
In this answer, I'll combine #1 & #3 in a model object, and show two examples for #2. There are lots of ways you could make this happen, so play around and see what works best for you.
Note that there is a lot of code just to style the views, so you can see what's going on. I've commented the critical bits.
Pictures (opacity method on left, offset method on right)
The model (this satisfies #1 & #3)
class LoginStateModel: ObservableObject {
// changing this will change the main view
@Published var loggedIn = false
// will store the username typed on the LoginView
@Published var username = ""
func login() {
// simulating successful API call
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// when we log in, animate the result of setting loggedIn to true
// (i.e., animate showing MainView)
withAnimation(.default) {
self.loggedIn = true
}
}
}
}
The top-level view (this satisfies #2)
struct ContentView: View {
@ObservedObject var model = LoginStateModel()
var body: some View {
ZStack {
// just here for background
Color(UIColor.cyan).opacity(0.3)
.edgesIgnoringSafeArea(.all)
// we show either LoginView or MainView depending on our model
if model.loggedIn {
MainView()
} else {
LoginView()
}
}
// this passes the model down to descendant views
.environmentObject(model)
}
}
The default transition for adding and removing views from the view hierarchy is to change their opacity. Since we wrapped our changes to model.loggedIn
in withAnimation(.default)
, this opacity change will happen slowly (its better on a real device than the compressed GIFs below).
Alternatively, instead of having the views fade in/out, we could have them move on/off screen using an offset. For the second example, replace the if
/else
block above (including the if
itself) with
MainView()
.offset(x: model.loggedIn ? 0 : UIScreen.main.bounds.width, y: 0)
LoginView()
.offset(x: model.loggedIn ? -UIScreen.main.bounds.width : 0, y: 0)
The login view
struct LoginView: View {
@EnvironmentObject var model: LoginStateModel
@State private var usernameString = ""
@State private var passwordString = ""
var body: some View {
VStack(spacing: 15) {
HStack {
Text("Username")
Spacer()
TextField("Username", text: $usernameString)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
HStack {
Text("Password")
Spacer()
SecureField("Password", text: $passwordString)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
Button(action: {
// save the entered username, and try to log in
self.model.username = self.usernameString
self.model.login()
}, label: {
Text("Login")
.font(.title)
.inExpandingRectangle(Color.blue.opacity(0.6))
})
.buttonStyle(PlainButtonStyle())
}
.padding()
.inExpandingRectangle(Color.gray)
.frame(width: 300, height: 200)
}
}
Note that in a real functional login form, you'd want to do some basic input sanitation and disable/rate limit the login button so you don't get a billion server requests if someone spams the button.
For inspiration, see:
Introducing Combine (WWDC Session)
Combine in Practice (WWDC Session)
Using Combine (UIKit example, but shows how to throttle network requests)
The main view
struct MainView: View {
@EnvironmentObject var model: LoginStateModel
var body: some View {
VStack(spacing: 15) {
ZStack {
Text("Hello \(model.username)!")
.font(.title)
.inExpandingRectangle(Color.blue.opacity(0.6))
.frame(height: 60)
HStack {
Spacer()
Button(action: {
// when we log out, animate the result of setting loggedIn to false
// (i.e., animate showing LoginView)
withAnimation(.default) {
self.model.loggedIn = false
}
}, label: {
Text("Logout")
.inFittedRectangle(Color.green.opacity(0.6))
})
.buttonStyle(PlainButtonStyle())
.padding()
}
}
Text("Content")
.inExpandingRectangle(.gray)
}
.padding()
}
}
Some convenience extensions
extension View {
func inExpandingRectangle(_ color: Color) -> some View {
ZStack {
RoundedRectangle(cornerRadius: 15)
.fill(color)
self
}
}
func inFittedRectangle(_ color: Color) -> some View {
self
.padding(5)
.background(RoundedRectangle(cornerRadius: 15)
.fill(color))
}
}
iOS SwiftUI: pop or dismiss view programmatically
This example uses the new environment var documented in the Beta 5 Release Notes, which was using a value property. It was changed in a later beta to use a wrappedValue property. This example is now current for the GM version. This exact same concept works to dismiss Modal views presented with the .sheet modifier.
import SwiftUI
struct DetailView: View {
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
Button(
"Here is Detail View. Tap to go back.",
action: { self.presentationMode.wrappedValue.dismiss() }
)
}
}
struct RootView: View {
var body: some View {
VStack {
NavigationLink(destination: DetailView())
{ Text("I am Root. Tap for Detail View.") }
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
RootView()
}
}
}
Related Topics
What's the Difference Between Using Aranchor to Insert a Node and Directly Insert a Node
Swift: How to Read Standard Output in a Child Process Without Waiting for Process to Finish
Add @Published Behaviour for Computed Property
How to Add Material to Modelentity Programatically in Realitykit
Self' Used Before All Stored Properties Are Initialized
Swift Equivalent to _Attribute((Objc_Requires_Super))
Convert Firebase Firestore Timestamp to Date (Swift)
How to Convert Range in Nsrange
Swift: Extending Functionality of Print() Function
What Are "Intervals" in Swift Ranges
What Does the Swiftui '@State' Keyword Do
Convert Uiimage to Grayscale Keeping Image Quality
Swiftui Create Image Slider with Dots as Indicators
Replacing Calayer and Cabasicanimation with Skscene and Skactions
How to Convert Uicolor to Swiftui's Color