SwiftUI adding custom UIViewControllerTransitioningDelegate - ios

I'm trying to create SwiftUI custom segue animation, like this
I try to chain the animation to Destination but no use, it just animates inside the contents of Destination after presentation animation finish.
struct ContentView : View {
var body: some View {
NavigationView {
VStack {
List {
NavigationButton(destination: withAnimation{
Destination()
}.frame(width: 300, height: 300)
.animation(.basic())) {
CellView()
}
}
}
}
}
}
struct CellView : View {
var body: some View {
VStack {
Text("Cell")
}
}
}
struct Destination : View {
var body: some View {
VStack {
Text("GFD")
}
}
}

you can try with removing the animation

Related

SwiftUI - Update name of the button to what parent view has on it

I have ImageSettingView which Im using multiple places. When I used this view at multiple place in my project it takes static name all time in the navigationBarItem i.e Text("Image Collab View"). I want to give dynamic name change based on where parent view it comes from.
ImageColorView -> ImageSettingView (Back NavButon text - ImageColor ) ProfilePictureView -> ImageSettingView (Back NavButon text - ProfilePictureView )
Sample code of ImageSettingView :-
struct ImageSettingView : View {
var body: some View {
GeometryReader { geometry in
VStack {
Form {
Text("Image View")
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(
leading:
Button(action: {
onUserImage()
}) {
HStack {
Image(systemName: "backward")
Text("Image")
// I want to use change above text based on what name of the parent view was
}
},
trailing:
Button(action: {
onDelete()
}) {
HStack {
Text("Delete")
}
}
)
}
}
}
Sample image of mock up
Since you always know what the parent view is, try this approach, passing the name of the parent view to your ImageSettingView.
struct ImageSettingView: View {
let parentName: String // <-- here
//...
Image(systemName: "backward")
Text(parentName) // <-- here
//...
and call it like this:
ImageSettingView(parentName: "TestView")
or
ImageSettingView(parentName: "ProfilePictureView")
or
ImageSettingView(parentName: "\(Self.self)")
EDIT-1: this is the test code I used to show my answer works:
struct ContentView: View {
var body: some View {
NavigationStack {
VStack(spacing: 50) {
Text("This is ContentView")
NavigationLink("To ProfileView", destination: ProfileView(parentName: "ContentView"))
}
}
}
}
struct ProfileView: View {
let parentName: String
var body: some View {
VStack(spacing: 50) {
Text("This is ProfileView")
ImageSettingView(parentName: parentName)
NavigationLink("To Next View ", destination: NextView(parentName: "\(Self.self)"))
}
}
}
struct NextView: View {
let parentName: String
var body: some View {
VStack(spacing: 50) {
Text("This is NextView")
ImageSettingView(parentName: parentName)
}
}
}
struct ImageSettingView: View {
#Environment(\.dismiss) var dismiss
let parentName: String // <--here
var body: some View {
GeometryReader { geometry in
VStack {
Form {
Text("Image View")
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(
leading:
Button(action: {
// onUserImage()
dismiss()
}) {
HStack {
Image(systemName: "backward")
Text(parentName) // <--here
}
},
trailing:
Button(action: {
// onDelete()
}) {
HStack {
Text("Delete")
}
}
)
}
}
}
}

SwiftUI 4.0: NavigationStack second Subview, movement and back link broken

I'm using NavigationStack and struggling with Navigationslinks on second SubView depth.
struct ContentView: View {
var body: some View {
NavigationStack {
VStack {
Text("I'm a MainView").font(.title2).padding()
NavigationLink {
SubChild1()
} label: {
Text("Goto SubView 1").font(.title3)
}
Spacer()
}
}
}
}
struct SubChild1: View {
var body: some View {
VStack {
Text("I'm SubView 1").font(.title2)
.padding()
NavigationLink {
SubChild2()
} label: {
Text("Goto SubView 2").font(.title3)
}
Spacer()
}
}
}
struct SubChild2: View {
var body: some View {
VStack {
Text("I'm a SubView 2").font(.title2).padding()
Spacer()
}
}
}
When I navigate to Subview 1, animation work. If I navigate then to Subview2 the view appear immediately without animation.
The "Back" link on upper left corner navigates back to the root main view controller instead of subview 1.
Did I miss something or is that a known "feature"?
So after some testing. If I change to:
enum Links: Hashable {
case sub1, sub2, others
}
struct ContentView: View {
let links = Links.sub1
var body: some View {
NavigationStack {
VStack {
Text("I'm a MainView").font(.title2).padding()
NavigationLink(value: Links.sub1) {
Text("Goto SubView 1").font(.title3)
}
.navigationDestination(for: Links.self) { link in
switch link {
case .sub1:
SubChild1()
case .sub2:
SubChild2()
default:
Text("You reached the end of all Views")
}
}
}
Spacer()
}
}
}
struct SubChild1: View {
var body: some View {
VStack {
Text("I'm SubView 1").font(.title2)
.padding()
NavigationLink(value: Links.sub2) {
Text("Goto SubView 2").font(.title3)
}
.padding(.bottom,20)
NavigationLink(value: Links.others) {
Text("End of all Views").font(.title3)
}
.navigationDestination(for: Links.self) { link in
switch link {
case .sub1:
SubChild1()
case .sub2:
SubChild2()
default:
Text("You reached the end of all views")
}
}
Spacer()
}
}
}
struct SubChild2: View {
var body: some View {
VStack {
Text("I'm a SubView 2").font(.title2).padding()
Spacer()
}
}
}
The navigation works now as expected, but I'have to implement an D.Type eg. Enum with a case for every link.
Using e.g. one case doesn't work and on console I get the message on subview1:
A navigationDestination for “NavigationStackDemo.Links” was declared earlier on the stack. Only the destination declared closest to the root view of the stack will be used.
and instead of subview2 is added subview1 is added as destination.

pop to specific View in SwiftUI like we use "popToViewController"

Is it possible in SwiftUI to come back to a specific view? Like when we had controllers
The code was this as mention below
let arr = self.navigationController?.viewControllers
for controller in arr!{
if controller.isKind(of: ViewController.classForCoder()){
self.navigationController?.popToViewController(controller, animated: true)
}
}
Now Let suppose I have 4 Views:-> as mentioned in the code
struct View1: View {
var body: some View {
NavigationView {
NavigationLink(destination: View2()) {
Text("Navigate to View2")
}
}
}
}
struct View2: View {
var body: some View {
NavigationLink(destination: View3()) {
Text("Navigate to View3")
}
}
}
struct View3: View {
var body: some View {
NavigationLink(destination: View4()) {
Text("Navigate to View4")
}
}
}
struct View4: View {
var body: some View {
Text("4")
}
}
Is it possible to pop from View4 to View2??? "Skipping View 3"
or From View3 to View 1
Likewise UIKit

Come back to a specific View in SwiftUI (popToViewController)

Is it possible in SwiftUI to come back to a specific view? Let's say I have three views this way:
struct View1: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: View2()) {
Text("Navigate to View2")
}
.navigationBarTitle("View1")
}
}
}
}
struct View2: View {
var body: some View {
NavigationLink(destination: View3()) {
Text("Navigate to View3")
}
.navigationBarTitle("View2")
}
}
struct View3: View {
var body: some View {
Text("View3!")
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
View1()
}
}
#endif
The navigation works back and forth:
View1->View2->View3
View3->View2->View1
Is it possible to directly come back to View1 from the View3? What I'm looking for is something similar to the UIKit
func popToViewController(_ viewController: UIViewController,
animated: Bool) -> [UIViewController]?
Trying to solve this issue I ended up creating an open source project called swiftui-navigation-stack (https://github.com/biobeats/swiftui-navigation-stack). It contains the NavigationStackView, a view that mimics all the navigation behaviours of the standard NavigationView, adding some other features (all the features are explained in the readme of the repo). To answer the question here above we can use the NavigationStackView this way:
Let's pretend we have to implement a navigation like this:
View1 (push)-> View2 (push)-> View3 (push)-> View4 (pop)-> View2
First of all embed your first view in a NavigationStackView (as you'd do with the standard NavigationView):
struct RootView: View {
var body: some View {
NavigationStackView {
View1()
}
}
}
Let's create these simple views to build the example:
struct View1: View {
var body: some View {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 1")
Spacer()
PushView(destination: View2(), destinationId: "view2") {
Text("PUSH TO VIEW 2")
}
}
}
}
}
struct View2: View {
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 2")
Spacer()
PushView(destination: View3()) {
Text("PUSH TO VIEW 3")
}
}
}
}
}
struct View3: View {
var body: some View {
ZStack {
Color.gray.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 3")
Spacer()
PushView(destination: View4()) {
Text("PUSH TO VIEW 4")
}
}
}
}
}
struct View4: View {
var body: some View {
ZStack {
Color.white.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 4")
Spacer()
PopView(destination: .view(withId: "view2")) {
Text("POP TO VIEW 2")
}
}
}
}
}
PushView and PopView let you navigate between views and, among other things, they let you specify an identifier for a view (so that you can come back to it if you need).
The following is the complete example, you can copy-paste it to xCode to try it yourself:
import SwiftUI
import NavigationStack
struct RootView: View {
var body: some View {
NavigationStackView {
View1()
}
}
}
struct View1: View {
var body: some View {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 1")
Spacer()
PushView(destination: View2(), destinationId: "view2") {
Text("PUSH TO VIEW 2")
}
}
}
}
}
struct View2: View {
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 2")
Spacer()
PushView(destination: View3()) {
Text("PUSH TO VIEW 3")
}
}
}
}
}
struct View3: View {
var body: some View {
ZStack {
Color.gray.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 3")
Spacer()
PushView(destination: View4()) {
Text("PUSH TO VIEW 4")
}
}
}
}
}
struct View4: View {
var body: some View {
ZStack {
Color.white.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 4")
Spacer()
PopView(destination: .view(withId: "view2")) {
Text("POP TO VIEW 2")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
RootView()
}
}
The result is:

iOS SwiftUI: pop or dismiss view programmatically

I couldn't find any reference about any ways to make a pop or a dismiss programmatically of my presented view with SwiftUI.
Seems to me that the only way is to use the already integrated slide dow action for the modal(and what/how if I want to disable this feature?), and the back button for the navigation stack.
Does anyone know a solution?
Do you know if this is a bug or it will stays like this?
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()
}
}
}
SwiftUI Xcode Beta 5
First, declare the #Environment which has a dismiss method which you can use anywhere to dismiss the view.
import SwiftUI
struct GameView: View {
#Environment(\.presentationMode) var presentation
var body: some View {
Button("Done") {
self.presentation.wrappedValue.dismiss()
}
}
}
iOS 15+
Starting from iOS 15 we can use a new #Environment(\.dismiss):
struct SheetView: View {
#Environment(\.dismiss) var dismiss
var body: some View {
NavigationView {
Text("Sheet")
.toolbar {
Button("Done") {
dismiss()
}
}
}
}
}
(There's no more need to use presentationMode.wrappedValue.dismiss().)
Useful links:
DismissAction
There is now a way to programmatically pop in a NavigationView, if you would like. This is in beta 5. Notice that you don't need the back button. You could programmatically trigger the showSelf property in the DetailView any way you like. And you don't have to display the "Push" text in the master. That could be an EmptyView(), thereby creating an invisible segue.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
MasterView()
}
}
}
struct MasterView: View {
#State private var showDetail = false
var body: some View {
VStack {
NavigationLink(destination: DetailView(showSelf: $showDetail), isActive: $showDetail) {
Text("Push")
}
}
}
}
struct DetailView: View {
#Binding var showSelf: Bool
var body: some View {
Button(action: {
self.showSelf = false
}) {
Text("Pop")
}
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
I recently created an open source project called swiftui-navigation-stack (https://github.com/biobeats/swiftui-navigation-stack) that contains the NavigationStackView, an alternative navigation stack for SwiftUI. It offers several features described in the readme of the repo. For example, you can easily push and pop views programmatically. I'll show you how to do that with a simple example:
First of all embed your hierarchy in a NavigationStackVew:
struct RootView: View {
var body: some View {
NavigationStackView {
View1()
}
}
}
NavigationStackView gives your hierarchy access to a useful environment object called NavigationStack. You can use it to, for instance, pop views programmatically as asked in the question above:
struct View1: View {
var body: some View {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 1")
Spacer()
PushView(destination: View2()) {
Text("PUSH TO VIEW 2")
}
}
}
}
}
struct View2: View {
#EnvironmentObject var navStack: NavigationStack
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 2")
Spacer()
Button(action: {
self.navStack.pop()
}, label: {
Text("PROGRAMMATICALLY POP TO VIEW 1")
})
}
}
}
}
In this example I use the PushView to trigger the push navigation with a tap. Then, in the View2 I use the environment object to programmatically come back.
Here is the complete example:
import SwiftUI
import NavigationStack
struct RootView: View {
var body: some View {
NavigationStackView {
View1()
}
}
}
struct View1: View {
var body: some View {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 1")
Spacer()
PushView(destination: View2()) {
Text("PUSH TO VIEW 2")
}
}
}
}
}
struct View2: View {
#EnvironmentObject var navStack: NavigationStack
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 2")
Spacer()
Button(action: {
self.navStack.pop()
}, label: {
Text("PROGRAMMATICALLY POP TO VIEW 1")
})
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
RootView()
}
}
the result is:
Alternatively, if you don't want to do it programatically from a button, you can emit from the view model whenever you need to pop.
Subscribe to a #Published that changes the value whenever the saving is done.
struct ContentView: View {
#ObservedObject var viewModel: ContentViewModel
#Environment(\.presentationMode) var presentationMode
init(viewModel: ContentViewModel) {
self.viewModel = viewModel
}
var body: some View {
Form {
TextField("Name", text: $viewModel.name)
.textContentType(.name)
}
.onAppear {
self.viewModel.cancellable = self.viewModel
.$saved
.sink(receiveValue: { saved in
guard saved else { return }
self.presentationMode.wrappedValue.dismiss()
}
)
}
}
}
class ContentViewModel: ObservableObject {
#Published var saved = false // This can store any value.
#Published var name = ""
var cancellable: AnyCancellable? // You can use a cancellable set if you have multiple observers.
func onSave() {
// Do the save.
// Emit the new value.
saved = true
}
}
Please check Following Code it's so simple.
FirstView
struct StartUpVC: View {
#State var selection: Int? = nil
var body: some View {
NavigationView{
NavigationLink(destination: LoginView().hiddenNavigationBarStyle(), tag: 1, selection: $selection) {
Button(action: {
print("Signup tapped")
self.selection = 1
}) {
HStack {
Spacer()
Text("Sign up")
Spacer()
}
}
}
}
}
SecondView
struct LoginView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView{
Button(action: {
print("Login tapped")
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image("Back")
.resizable()
.frame(width: 20, height: 20)
.padding(.leading, 20)
}
}
}
}
}
You can try using a custom view and a Transition.
Here's a custom modal.
struct ModalView<Content>: View where Content: View {
#Binding var isShowing: Bool
var content: () -> Content
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .center) {
if (!self.isShowing) {
self.content()
}
if (self.isShowing) {
self.content()
.disabled(true)
.blur(radius: 3)
VStack {
Text("Modal")
}
.frame(width: geometry.size.width / 2,
height: geometry.size.height / 5)
.background(Color.secondary.colorInvert())
.foregroundColor(Color.primary)
.cornerRadius(20)
.transition(.moveAndFade) // associated transition to the modal view
}
}
}
}
}
I reused the Transition.moveAndFade from the Animation Views and Transition tutorial.
It is defined like this:
extension AnyTransition {
static var moveAndFade: AnyTransition {
let insertion = AnyTransition.move(edge: .trailing)
.combined(with: .opacity)
let removal = AnyTransition.scale()
.combined(with: .opacity)
return .asymmetric(insertion: insertion, removal: removal)
}
}
You can test it - in the simulator, not in the preview - like this:
struct ContentView: View {
#State var isShowingModal: Bool = false
func toggleModal() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
withAnimation {
self.isShowingModal = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
withAnimation {
self.isShowingModal = false
}
}
}
}
var body: some View {
ModalView(isShowing: $isShowingModal) {
NavigationView {
List(["1", "2", "3", "4", "5"].identified(by: \.self)) { row in
Text(row)
}.navigationBarTitle(Text("A List"), displayMode: .large)
}.onAppear { self.toggleModal() }
}
}
}
Thanks to that transition, you will see the modal sliding in from the trailing edge, and the it will zoom and fade out when it is dismissed.
The core concept of SwiftUI is to watch over the data flow.
You have to use a #State variable and mutate the value of this variable to control popping and dismissal.
struct MyView: View {
#State
var showsUp = false
var body: some View {
Button(action: { self.showsUp.toggle() }) {
Text("Pop")
}
.presentation(
showsUp ? Modal(
Button(action: { self.showsUp.toggle() }) {
Text("Dismiss")
}
) : nil
)
}
}
I experienced a compiler issue trying to call value on the presentationMode binding. Changing the property to wrappedValue fixed the issue for me. I'm assuming value -> wrappedValue is a language update. I think this note would be more appropriate as a comment on Chuck H's answer but don't have enough rep points to comment, I also suggested this change as and edit but my edit was rejected as being more appropriate as a comment or answer.
This will also dismiss the view
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
window?.rootViewController?.dismiss(animated: true, completion: {
print("dismissed")
})

Resources