Transition from one view to another SwiftUI - view

How can I transition from one View to another? Is there push / pop like in UIKit? Do I have to use a NavigationView and if so how?

Using a NavigationView
NavigationViews are tied to NavigationButton (AFAIK that is the only way to trigger a segue). Here is a simple example where the main view can transition to a detail view.
struct DetailView : View {
let value: String
var body : some View {
Text("Full View: \(value)")
}
}
struct MainView : View {
var body : some View {
NavigationView {
NavigationButton(destination: DetailView(value: "Hello World"),
label: { Text("Click Me") })
}
}
}
This will automatically handle transitions and add a back button.
Using State
Another approach is to use a stateful variable to determine if the child view is displayed. Here is another simple example:
struct DetailView : View {
let value: String
let onDone: () -> Void
var body : some View {
VStack {
Text("Full View: \(value)")
Button(action: onDone, label: { Text("Back") })
}
}
}
struct MainView : View {
#State var displaySubview = false
var body : some View {
VStack {
if displaySubview {
DetailView(value: "Hello World") {
self.displaySubview = false
}
} else {
Button(action: {
self.displaySubview = true
}, label: { Text("Click Me") })
}
}
}
}
This approach requires you to implement more of the UI elements, but it also allows for more control over views.

It works for me
struct ContentView : View {
var body : some View {
NavigationView {
NavigationLink(destination: DetailView(), label: { Text("To Detail")})
}
}}

Related

Swiftui how to show an image if user selected a button 2 views before

I want to show a flag in the main view if the user selected that country 2 views before getting to the main view.
The code for the page where the user selects the country is:
struct ChooseLanguageWithButtonView: View {
#State var isSelected1 = false
#State var isSelected2 = false
var body: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
VStack {
ScrollView (showsIndicators: false) {
VStack(spacing: 20){
VStack(alignment: .leading){
Text("Choose the language you want to learn!")
.font(.custom("Poppins-Bold", size: 25))
Text("Select one language")
.opacity(0.5)
.font(.custom("Poppins-Light", size: 15))
.frame(alignment: .bottomTrailing)
.padding(5)
Spacer()
}
.padding(.top, -25)
HStack (spacing: 30){
ButtonLanguage(
isSelected: $isSelected1,
color: .orangeGradient1,
textTitle: "English",
textSubtitle: "American",
imageLang: "englishAmerican",
imageFlag: "1"
)
.onTapGesture(perform: {
isSelected1.toggle()
if isSelected1 {
isSelected2 = false
}
})
ButtonLanguage(
isSelected: $isSelected2,
color: .orangeGradient1,
textTitle: "English",
textSubtitle: "British",
imageLang: "telephone",
imageFlag: "0"
)
.onTapGesture(perform: {
isSelected2.toggle()
if isSelected2 {
isSelected1 = false
}
})
}
NavigationLink(destination: {
if isSelected1 {
EnglishAmericanLevelView()
} else if isSelected2 {
EnglishBritishView()
}
}, label: {
Text("Let's Go")
.bold()
.foregroundColor(Color.white)
.frame(width: 200, height: 50)
.cornerRadius(40)
})
On the main view if the user chose english american I want to show the american flag.
Someone can help me with that please?
Assuming you are using MVVM pattern, then you can create a published Bool variable in the ViewModel and pass it in the environment.
Something passed in the environment can be accessed from all descendents of the view.
class MainViewModel: ObservableObject {
#Published var showAmericanFlag: Bool = false
}
Pass the ViewModel it to either the MainView or ChildView as an environment object.
// Wherever you are using MainView
MainView()
.environmentObject(MainViewModel())
// or create MainViewModel inside MainView and pass it to ChildView
struct MainView: View {
private var mainViewModel = MainViewModel()
var body: some View {
// stuff
ChildView()
.environmentObject(mainViewModel())
// stuff
}
}
You can get a reference to the MainViewModel inside the ChildView from the
environment and use the showAmericanFlag variable.
struct ChildView: View {
#EnvironmentObject private var mainViewModel: MainViewModel
var body: some View {
// stuff
if mainViewModel.showAmericanFlag {
Image("americanFlag")
}
// stuff
}
}
It sounds like a global class might be good to use here, so you can set variables like this that later you can reference in your views.
final class GlobalClass: ObservableObject {
#Published public var showFlag: Bool = false
}
In your main project app file you can initialize the class with the .environmentObject method
import SwiftUI
#main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(GlobalClass())
}
}
}
You can then reference the global class in any view as follows
#EnvironmentObject private var globalObj: GlobalClass
Then you can set the variable to be whatever you'd like and then use it in an if statement to show your image. For example . . .
if (globalObj.showFlag){
Image("flag").onTapGesture{
globalObj.showFlag = false
}
}
Otherwise you will have to pass the show flag object from view to view

SwiftUI: animating tab item addition/removal in tab bar

In my app I add/remove a subview to/from a TabView based on some condition. I'd like to animate tab item addition/removal in tab bar. My experiment (see code below) shows it's not working. I read on the net that TabView support for animation is quite limited and some people rolled their own implementation. But just in case, is it possible to implement it?
import SwiftUI
struct ContentView: View {
#State var showBoth: Bool = false
var body: some View {
TabView {
Button("Test") {
withAnimation {
showBoth.toggle()
}
}
.tabItem {
Label("1", systemImage: "1.circle")
}
if showBoth {
Text("2")
.tabItem {
Label("2", systemImage: "2.circle")
}
.transition(.slide)
}
}
}
}
Note: moving transition() call to the Label passed to tabItem() doesn't work either.
As commented Apple wants the TabBar to stay unchanged throughout the App.
But you can simply implement your own Tabbar with full control:
struct ContentView: View {
#State private var currentTab = "One"
#State var showBoth: Bool = false
var body: some View {
VStack {
TabView(selection: $currentTab) {
// Tab 1.
VStack {
Button("Toggle 2. Tab") {
withAnimation {
showBoth.toggle()
}
}
} .tag("One")
// Tab 2.
VStack {
Text("Two")
} .tag("Two")
}
// custom Tabbar buttons
Divider()
HStack {
OwnTabBarButton("One", imageName: "1.circle")
if showBoth {
OwnTabBarButton("Two", imageName: "2.circle")
.transition(.scale)
}
}
}
}
func OwnTabBarButton(_ label: String, imageName: String) -> some View {
Button {
currentTab = label
} label: {
VStack {
Image(systemName: imageName)
Text(label)
}
}
.padding([.horizontal,.top])
}
}

Highlight Navigation View At Start

When I start my app, the start page is "Kunde" but the whole thing is not highlighted in blue in the navigation. It just turns blue (system color) when I click on it.
I want it to be highlighted blue when I open the app.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: ListView()) {
Text("Kunde")
}
}
ListView()
}
}
}
struct ListView: View {
var body: some View {
Text("Hello.")
}
}
you could try something like this approach:
struct ContentView: View {
#State var selection: String?
#State var listData = ["Kunde", "xxxx", "zzzz"]
var body: some View {
NavigationView {
List(listData, id: \.self) { item in
NavigationLink(destination: ListView()) {
Text(item)
}
.listRowBackground(selection == item ? Color.blue : Color.clear)
}
}
.onAppear {
selection = "Kunde"
}
}
}

SwiftUI for macOS - trigger sheet .onDismiss problem

In a multiplatform app I'm showing a sheet to collect a small amount of user input. On iOS, when the sheet is dismissed, the relevant .onDismiss method is called but not on macOS.
I've read that having the .onDismiss in the List can cause problems so I've attached it to the button itself with no improvement. I've also tried passing the isPresented binding through and toggling that within the sheet itself to dismiss, but again with no success.
I am employing a NavigationView but removing that makes no difference. The following simplified example demonstrates my problem. Any ideas? Should I even be using a sheet for this purpose on macOS?
I just want to make clear that I have no problem closing the sheet. The other questions I found were regarding problems closing the sheet - I can do that fine.
import SwiftUI
#main
struct SheetTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
ListView()
}
}
}
The List view.
struct ListView: View {
#State private var isPresented: Bool = false
var body: some View {
VStack {
Text("Patterns").font(.title)
Button(action: {
isPresented = true
}, label: {
Text("Add")
})
.sheet(isPresented: $isPresented, onDismiss: {
doSomethingAfter()
}) {
TestSheetView()
}
List {
Text("Bingo")
Text("Bongo")
Text("Banjo")
}
.onAppear(perform: {
doSomethingBefore()
})
}
}
func doSomethingBefore() {
print("Johnny")
}
func doSomethingAfter() {
print("Cash")
}
}
This is the sheet view.
struct TestSheetView: View {
#Environment(\.presentationMode) var presentationMode
#State private var name = ""
var body: some View {
Form {
TextField("Enter name", text: $name)
.padding()
HStack {
Spacer()
Button("Save") {
presentationMode.wrappedValue.dismiss()
}
Spacer()
}
}
.frame(minWidth: 300, minHeight: 300)
.navigationTitle("Jedward")
}
}
Bad issue.. you are right. OnDismiss is not called. Here is a workaround with Proxybinding
var body: some View {
VStack {
Text("Patterns").font(.title)
Button(action: {
isPresented = true
}, label: {
Text("Add")
})
List {
Text("Bingo")
Text("Bongo")
Text("Banjo")
}
.onAppear(perform: {
doSomethingBefore()
})
}
.sheet(isPresented: Binding<Bool>(
get: {
isPresented
}, set: {
isPresented = $0
if !$0 {
doSomethingAfter()
}
})) {
TestSheetView()
}
}

SwiftuUI NavigationLink inside touchBar

I'm trying to create NavigationLink in MacBook touchBar with help of SwiftUI. Actually with my piece of code, the button is shown in touchbar, but unfortunately the link doesn't work.
NavigationView {
.touchBar {
NavigationLink(destination: BookView()) {
Text("GoToBook")
}
}
}
struct BookView: View {
var body: some View {
Text("Hello")
}
}
Try instead with Button in touchBar activating NavigationLink programmatically, like below
#State private var isActive = false
...
// below in body
NavigationView {
SomeView() // << your view here
.background(NavigationLink(destination: BookView(), isActive: $isActive) {
EmptyView()
} // hidden link
)
.touchBar {
Button("GoToBook") { self.isActive.toggle() } // activate link
}
}

Resources