Push View programmatically in callback, SwiftUI
I've found the answer. If you want to show another view on callback you should
Create state
@State var pushActive = false
When ViewModel notifies that login is successful set
pushActive
totrue
func handleSuccessfullLogin() {
self.pushActive = true
print("handleSuccessfullLogin")
}Create hidden
NavigationLink
and bind to that stateNavigationLink(destination:
ProfileView(viewModel: ProfileViewModelImpl()),
isActive: self.$pushActive) {
EmptyView()
}.hidden()
Push View Programmatically After State Variable Defined
You could make your newProject
an optional, since you don't really want the value until it's created. Then, you can use an if let
optional binding to only return the destination to ProjectSideBar
available if there's a value.
Here's a simplified version of your code:
struct Project {
var name: String
}
struct FirstView: View {
@State var newProject: Project?
@State var pushNewProject:Bool = false
@ViewBuilder var navDestination : some View {
if let newProject = newProject {
ProjectSideBar(project: newProject)
} else {
EmptyView()
}
}
var body: some View {
NavigationView {
VStack {
Button(action: {
self.newProject = Project(name: "test")
self.pushNewProject = true
}){
Text("Create Project")
}
NavigationLink(destination: navDestination,
isActive: self.$pushNewProject) {
EmptyView()
}.hidden()
}
}
}
}
struct ProjectSideBar : View {
var project : Project
var body: some View {
Text(project.name)
}
}
Note that I tried using an if let
around the NavigationLink
first, without having to do the navDestination
computed property, but that results in a situation where there's not an animation to the new view. I believe this used to work, but as of iOS 15, it seems to need the NavigationLink
in the hierarchy the whole time to get the animation.
Push, Segue, Summon, Navigate to View Programmatically SwiftUI
A couple of things. First, NavigationLink must be imbedded in a NavigationView to work. Second, the link doesn't need a view as you showed it. This should show the second view. I will leave to you to update the other elements.
var body: some View {
NavigationView{
VStack {
Text("This is the ContentView")
Toggle(isOn: $show) {
Text("Toggle var show")
}
.padding()
Button(action: {
self.show = !self.show
}, label: {
Text(self.show ? "Off" : "On")
})
Text(String(show))
//this does not work - the ContentView is still shown
NavigationLink(destination: SecondView()){
Text("Click to View")}
Spacer()
// {
// EmptyView()
// }
//this does not work - it adds SecondView to ContentView
//I want a new view here, not an addition
//to the ContentView()
// if show {
// //I want a new view here, not an addition to the ContentView()
// SecondView()
// }
}
}
}
Programmatically navigate to new view in SwiftUI
You can replace the next view with your login view after a successful login. For example:
struct LoginView: View {
var body: some View {
...
}
}
struct NextView: View {
var body: some View {
...
}
}
// Your starting view
struct ContentView: View {
@EnvironmentObject var userAuth: UserAuth
var body: some View {
if !userAuth.isLoggedin {
LoginView()
} else {
NextView()
}
}
}
You should handle your login process in your data model and use bindings such as @EnvironmentObject
to pass isLoggedin
to your view.
Note: In Xcode Version 11.0 beta 4, to conform to protocol 'BindableObject' the willChange property has to be added
import Combine
class UserAuth: ObservableObject {
let didChange = PassthroughSubject<UserAuth,Never>()
// required to conform to protocol 'ObservableObject'
let willChange = PassthroughSubject<UserAuth,Never>()
func login() {
// login request... on success:
self.isLoggedin = true
}
var isLoggedin = false {
didSet {
didChange.send(self)
}
// willSet {
// willChange.send(self)
// }
}
}
Push, pop view controller equivalent in SwiftUI
Root :
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(Model()))
iOS version 13.1 :
class Model: ObservableObject {
@Published var pushed = false
}
struct ContentView: View {
@EnvironmentObject var model: Model
var body: some View {
NavigationView {
VStack {
Button("Push") {
self.model.pushed = true
}
NavigationLink(destination: DetailView(), isActive: $model.pushed) { EmptyView() }
}
}
}
}
struct DetailView: View {
@EnvironmentObject var model: Model
var body: some View {
Button("Bring me Back") {
self.model.pushed = false
}
}
}
Removing the default back button and adding our own will let us get through, until the bug gets fixed by Apple.
class Model: ObservableObject {
@Published var pushed = false
}
struct ContentView: View {
@EnvironmentObject var model: Model
var body: some View {
NavigationView {
VStack {
Button("Push") {
self.model.pushed = true
}
NavigationLink(destination: DetailView(), isActive: $model.pushed) { EmptyView() }
}
}
}
}
struct DetailView: View {
@EnvironmentObject var model: Model
var body: some View {
Button("Bring me Back") {
self.model.pushed = false
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: MyBackButton(label: "Back!") {
self.model.pushed = false
})
}
}
struct MyBackButton: View {
let label: String
let closure: () -> ()
var body: some View {
Button(action: { self.closure() }) {
HStack {
Image(systemName: "chevron.left")
Text(label)
}
}
}
}
more to refer
Push view with data from asynchronous callback
Based on the if let
method from @pawello2222's answer, I've created this ViewModifier
that let's you push a View to the Navigation Stack. It follows the same pattern as the .sheet
modifier.
import SwiftUI
private struct PushPresenter<Item: Identifiable, DestinationView: View>: ViewModifier {
let item: Binding<Item?>
let destination: (Item) -> DestinationView
let presentationBinding: Binding<Bool>
init(item: Binding<Item?>, onDismiss: (() -> Void)? = nil, content: @escaping (Item) -> DestinationView) {
self.item = item
self.destination = content
presentationBinding = Binding<Bool> {
item.wrappedValue != nil
} set: { isActive in
if !isActive && item.wrappedValue != nil {
onDismiss?()
item.wrappedValue = nil
}
}
}
func body(content: Content) -> some View {
ZStack {
content
NavigationLink(
destination: item.wrappedValue == nil ? nil : destination(item.wrappedValue!),
isActive: presentationBinding,
label: EmptyView.init
)
}
}
}
extension View {
/// Pushed a View onto the navigation stack using the given item as a data source for the View's content. **Notice**: Make sure to use `.navigationViewStyle(StackNavigationViewStyle())` on the parent `NavigationView` otherwise using this modifier will cause a memory leak.
/// - Parameters:
/// - item: A binding to an optional source of truth for the view's presentation. When `item` is non-nil, the system passes the `item`’s content to the modifier’s closure. This uses a `NavigationLink` under the hood, so make sure to have a `NavigationView` as a parent in the view hierarchy.
/// - onDismiss: A closure to execute when poping the view of the navigation stack.
/// - content: A closure returning the content of the view.
func push<Item: Identifiable, Content: View>(
item: Binding<Item?>,
onDismiss: (() -> Void)? = nil,
content: @escaping (Item) -> Content) -> some View
{
self.modifier(PushPresenter.init(item: item, onDismiss: onDismiss, content: content))
}
}
And then use it like so:
struct NavigationExampleView: View {
@ObservedObject var vm: NavigationExampleViewModel
var body: some View {
Button("Get User") {
vm.getUser()
}
.push(item: $vm.pushUser, content: UserView.init)
}
}
Be sure to keep your implementing view in a NavigationView
for the push
to take effect.
Edit
I found an issue with this solution. If using a reference type as the binding item
, the object will not get deinitialised on dismiss. It seems like the content
is holding a reference to the item
. The Object will only the deinitialised once a new object is set on the binding (next time a screen is pushed).
This is also the case for @pawello2222's answer, but not an issue when using the sheet
modifier. Any suggestions on how to solve this would be much welcomed.
Edit 2
Adding .navigationViewStyle(StackNavigationViewStyle())
to the parent NavigationView
removes the memory leak for some reason. See answer here for more details.
Using the if-let
-method caused the push view transition to stop working. Instead pass nil
to the NavigationLink
s destination
when the data is not pressent. I've updated ViewModifier
to handle this.
Check if there is navigation, and present and push from a SwiftUI view
You could do this multiple ways: delegates, closures or heck even with combine publishers. The easiest way to get started is with an action closure I think. It could look something like this:
struct SwiftUIView: View {
let action: () -> Void
var body: some View {
Button("press me", action: action)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swiftUIView = SwiftUIView(action: handleButtonPress)
let hostingController = UIHostingController(rootView: swiftUIView)
addChild(hostingController)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(hostingController.view)
NSLayoutConstraint.activate([
hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
func handleButtonPress() {
print("TODO: Insert navigation controller logic here")
}
}
How to push a new View onto NavigationStack after Async call
You need to use NavigationLink(destination: tag: selection:)
and add a @State
var, i had called this as finished then you need to use NavigationLink(destination: tag: selection:)
where tag will be the expected value in this case true
, and in selection parameter you need to pass the @State
var in this case $finished
import SwiftUI
struct LandmarkList: View {
@State var finished : Bool? = nil
let landmark = landmarkData[0]
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: LandmarkDetails(currentLandmark: self.landmark), tag: true, selection: self.$finished) {
Button(action: {
self.dummyAsynCall() {
self.finished = true
}
}) {
Text("Button")
}
}
}
}.navigationBarTitle("test")
}
private func dummyAsynCall(_ onComplete: @escaping (() -> ())) {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
print("Call completed")
onComplete()
}
}
}
you can find better explanation here ;) https://mecid.github.io/2019/07/17/navigation-in-swiftui/
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
Where Is the Official Documentation for Cvopenglestexture Method Types
Is the Function 'Dlopen()' Private API
How to Create Custom View Programmatically in Swift Having Controls Text Field, Button etc
Change the Color of a Link in an Nsmutableattributedstring
How to Resize Uitableviewcell to Fit Its Content
Has Anyone Found a Good Way of Using the New iOS5 Keyboard Events
Keep Getting Error Messages When Compiling Newest Version of Admob on iOS6 Sdk
Uitableviewcell Selected Background Color on Multiple Selection
Swift 3: How to Add Watermark on Video? Avvideocompositioncoreanimationtool iOS 10 Issue
iOS 10 Heading Arrow for Mkuserlocation Dot
Gem Native Extension Error While Installing Cocoapods
How to Get the MAC Os X Firewall to Permanently Allow My iOS App
Will Appstore Reviewers Allow Us to Use Dynamic Library in iOS8
Peripheral and Central at the Same Time on iOS
Titletextattributes Uiappearance Font in iOS 7
Where to Highlight Uicollectionviewcell: Delegate or Cell
How to Use Uiviewcontrolleranimatedtransitioning with Uinavigationcontroller