Swiftui: Can't Get the Transition of a Detailview to a Zstack in the Mainview to Work

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:

  1. Some sort of state that determines which is being shown
  2. Some wrapping view that will transition between two layouts when #1 changes
  3. 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)

Opacity Transition
Offset Transition

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



Leave a reply



Submit