SwiftUI How to Navigate to Multiple Possible Views Depending on Conditional? - ios

Creating a Study App, if I click on the tabs Cue Card, Multiple Choice Test, or Practice Test I want to navigate to three different views possibly but I want to do that all from one Tab View where I have a navigationLink that is a variable or something that depends on whether I clicked the tab Cue Card, Multiple Choice Test, or Practice Test.
I also already tried doing this in the enum method from the video https://www.youtube.com/watch?v=sGiFR0IZCz4&t=53s but could not figure it out.
import SwiftUI
struct SelectionView: View {
var body: some View {
NavigationView{
VStack {
TabsView(selection: "Cue Cards")
TabsView(selection: "Multiple Choice")
TabsView(selection: "Practice Test")
}.navigationBarBackButtonHidden(true)
}
}
struct SelectionView_Previews: PreviewProvider {
static var previews: some View {
Group {
SelectionView().previewDevice("iPhone 8")
SelectionView().previewDevice("iPhone 11 Pro")
SelectionView().previewDevice("iPhone 11 Pro Max")
}
}
}
}
import SwiftUI
struct TabsView: View {
#State private var isActive = false
var selection = ""
var body: some View {
VStack{
//WANT CUECARDCATEGORYVIEW() TO BE A VARIABLE OR SOMETHING THAT CAN
LEAD TO CUE CARD VIEW, MULTIPLE CHOICE TEST VIEW, OR PRACTICE TEST VIEW.
NavigationLink(destination: CueCardCategoryView(), isActive:
$isActive)
{
Button(action: {isActive = true }) {
HStack{
Text("\(selection)")
.font(.system(size: 28, weight: .semibold))
.multilineTextAlignment(.center)
Spacer()
}
}
}.buttonStyle(PrimaryButtonStyle(fillColor: .primaryButton))
}.padding(15)
}
}
struct TabsView_Previews: PreviewProvider {
static var previews: some View {
NavigationView{
TabsView()
}.environment(\.colorScheme, .dark)
.environment(\.colorScheme, .light)
}
}

You can try this:
SelectionView
enum TabSelection {
case cueCards, multipleChoice, practiceTest
}
struct SelectionView: View {
var body: some View {
NavigationView {
VStack {
TabsView(title: "Cue Card", selection: .cueCards)
TabsView(title: "Multiple Choice", selection: .multipleChoice)
TabsView(title: "Practice Test", selection: .practiceTest)
}.navigationBarBackButtonHidden(true)
}
}
struct SelectionView_Previews: PreviewProvider {
static var previews: some View {
Group {
SelectionView().previewDevice("iPhone 8")
SelectionView().previewDevice("iPhone 11 Pro")
SelectionView().previewDevice("iPhone 11 Pro Max")
}
}
}
}
Tabs View
import SwiftUI
struct TabsView: View {
#State private var isActive = false
var title: String
var selection: TabSelection
var body: some View {
switch selection {
case .cueCards:
NavigationLink(destination: Text("My Custom View 1"), isActive: $isActive, label: {
HStack{
Text(title)
.font(.system(size: 28, weight: .semibold))
.multilineTextAlignment(.center)
Spacer()
}
})
case .multipleChoice:
NavigationLink(destination: Text("My Custom View 2"), isActive: $isActive, label: {
HStack{
Text(title)
.font(.system(size: 28, weight: .semibold))
.multilineTextAlignment(.center)
Spacer()
}
})
case .practiceTest:
NavigationLink(destination: Text("My Custom View 3"), isActive: $isActive, label: {
HStack{
Text(title)
.font(.system(size: 28, weight: .semibold))
.multilineTextAlignment(.center)
Spacer()
}
})
}
}
}
struct TabsView_Previews: PreviewProvider {
static var previews: some View {
NavigationView{
TabsView(title: "Cue Card", selection: .cueCards)
}.environment(\.colorScheme, .dark)
.environment(\.colorScheme, .light)
}
}
Replace the Text("My Custom View 1") / 2 / 3 with your destination views.

Related

When i am Switching the Tab in Swiftui Every Time new View is showing and my previous View was Vanished can anyone fix this?

When i am Switching the Tab in Swiftui Every Time new View is showing and my previous View was Vanished.I have Tried Userdefaults also but not working.
import SwiftUI
struct HomeView: View {
#State var selectedTabs = "house"
var body: some View {
ZStack(alignment: .bottom) {
Color("LightTabColor")
.edgesIgnoringSafeArea(.all)
VStack{
CardView(selectedTab: selectedTabs)
CustomTabBar(selectedTab: $selectedTabs)
.padding(.bottom)
}
}.onAppear {
let selectedTab = UserDefaults.standard.string(forKey: "selectedTabs")
if let selectedTab = selectedTab {
self.selectedTabs = selectedTab
}
}.onDisappear {
UserDefaults.standard.set(self.selectedTabs, forKey: "selectedTabs")
}
}
}
struct CardView: View {
var selectedTab: String
var body: some View {
ZStack{
Color("LightTabColor")
.cornerRadius(25)
.frame(maxWidth: .infinity,maxHeight: .infinity)
.padding(10)
.shadow(radius: 30)
VStack {
// Show the appropriate view based on the selected tab
switch selectedTab {
case "gear.circle":
NewAI()
case "house":
ImageAI()
case "message":
ChatAI()
default:
ImageAI()
}
Spacer()
}
.padding(.leading,10)
.padding(.bottom,10)
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}

Keyboard in a sheet's

I have a basic window with an input field (page 1), on top of it appears a popup window (page 2), inside of which there is also an input fields and buttons, which, when clicked, will bring up a small window with an input field (page 3). If there is no "Done" on the keyboard, the interface functions normally. If you add a "Done" button, it turns out that its color changes from system color blue to gray when moving from page 2 to page 3. Experimenting and wondering why this is so, I found that the toolbar on page 1 is responsible for the color of the button on page 3... If you change the color of the button on the toolbar on page 1 - it will change on the toolbar on page 3, and page 2 will not be affected. Also, adding buttons causes error: "[LayoutConstraints] Unable to simultaneously satisfy constraints." I want to understand why setting the button on the keyboard for Page 1, I also get a button when I type on Page 3? And why is it grayed out and not working? Why if I change the color for the button on Page 1, does it also change for that gray button on Page 3?
A small representative sample:
ContentView
import SwiftUI
struct ContentView: View {
#State private var bloodClucoseLvl: String = ""
#State private var isSheetShown: Bool = false
#FocusState private var focusField: Bool
var body: some View {
NavigationView {
List {
Section("Add your current blood glucose lvl") {
TextField("5,0 mmol/l", text: $bloodClucoseLvl)
.focused($focusField)
}
Section("Add food or drink") {
Button(action:{
isSheetShown.toggle()
}, label:{
HStack{
Text("Add")
Image(systemName: "folder.badge.plus")
}
})
.sheet(isPresented: $isSheetShown) {
addFoodButton()
}
}
}
.navigationTitle("Page 1 - General")
.toolbar{
ToolbarItem(placement: .keyboard) {
HStack {
Spacer()
Button(action: {
focusField = false
}) {
Text("Done")
}
}
}
}
.ignoresSafeArea(.keyboard)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
addFoodButton
import SwiftUI
struct addFoodButton: View {
#State private var selectedFood: String = ""
#State public var addScreen: Bool = false
var body: some View {
ZStack {
NavigationView {
List {
Section("or choose from category"){
NavigationLink(destination: Alcohol(addScreen: $addScreen)){
Text("Alcohol")
}
}
}
.listStyle(.insetGrouped)
.searchable(text: $selectedFood, prompt: "Search by word")
.navigationTitle("Page 2 - Search in DB")
}
if addScreen{
addScreenView(addScreen: $addScreen)
}
}
}
}
struct Alcohol: View {
#State private var searchInsideCategory: String = ""
#Binding var addScreen: Bool
var body: some View {
List {
Button(action: {addScreen.toggle()}){
Text("Light beer")
}
}
.navigationTitle("Page 2 - Choose beer")
.searchable(text: $searchInsideCategory, prompt: "Search inside a category")
}
}
struct addFoodButton_Previews: PreviewProvider {
static var previews: some View {
addFoodButton()
}
}
addScreenView
import SwiftUI
struct addScreenView: View {
#Binding var addScreen: Bool
#State private var gram: String = ""
var body: some View {
ZStack{
Color.black.opacity(0.2).ignoresSafeArea()
VStack(spacing:0){
Text("Page 3 - Add an item")
.bold()
.padding()
Divider()
VStack(){
TextField("gram", text: $gram)
.padding(.leading, 16)
.padding(.trailing, 16)
.keyboardType(.numberPad)
Rectangle()
.frame(height: 1)
.padding(.leading, 16)
.padding(.trailing, 16)
}.padding()
Divider()
HStack(){
Button(action: {
addScreen.toggle()
}){
Text("Cancel").frame(minWidth:0 , maxWidth: .infinity)
}
Divider()
Button(action: {
addScreen.toggle()
}){
Text("Save").frame(minWidth:0 , maxWidth: .infinity)
}
}.frame(height: 50)
}
.background(Color.white.cornerRadius(10))
.padding([.leading, .trailing], 15)
}
}
}
struct addScreenView_Previews: PreviewProvider {
static var previews: some View {
addScreenView(addScreen: .constant(true))
}
}

SwiftUI list multiple custom row edit option

I need a multiple rows edit option for SwiftUI list
My login screen in NavigationView
After login Home screen is tab
bar
one of the tab is list view (I need edit option for this
list)
Here is my total code
first screen I have only simple navigation view with text fields and button.
struct HomeView: View {
#ObservedObject var viewModel = HomeViewModel()
//#State var title = "Home"
var body: some View {
TabView(selection: $viewModel.selectedView) {
TasksView()
.tabItem {
Image(“one”)
Text(“one”)
}.tag(0)
DashboardView()
.tabItem {
Image(“two”)
Text(“two”)
}.tag(1)
NotifcationsView()
.tabItem {
Image(“some”)
Text(“some”)
}.tag(2)
SettingsView()
.tabItem {
Image(“set”)
Text("Se")
}.tag(3)
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(trailing: EditButton())
.navigationBarTitle(Text(viewModel.title) , displayMode: .inline)
}
}
struct TasksView: View {
#ObservedObject var viewModel = TViewModel()
#State var segmentSelection = 0
#State var selection = Set<String>()
// #State var editMode = EditMode.active
var body: some View {
VStack {
Picker(selection: $viewModel.segmentSelection, label: Text("")) {
ForEach(0..<self.viewModel.segments.count) {index in
Text(self.viewModel.segments[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
.padding(5)
//MyTaskListView()
List (selection: $selection) {
ForEach(viewModel.mt){ t in
//TaskCell(t : task)
Text("Title")
}
.onDelete(perform: viewModel.delete)
}
}.onAppear{
self.viewModel.requestMYTasks()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
}
init() {
UISegmentedControl.appearance().selectedSegmentTintColor = Colors.navBarColor
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: Colors.navBarColor], for: .normal)
}
}
after keeping simple row also , edit is not working
EditButton tracks edit mode automatically, so just remove your explicit state and all works (on replicated code, Xcode 11.4)
//#State var editMode = EditMode.active // remove this
List (selection: $selection) {
ForEach(viewModel.myTasks){ task in
TaskCell(task : task)
}
.onDelete(perform: viewModel.delete)
// .environment(\.editMode, self.$editMode) // ... and this
}
.navigationBarItems(trailing: EditButton())

SwiftUI Hide TabView bar inside NavigationLink views

I have a TabView and separate NavigationView stacks for every Tab item. It works well but when I open any NavigationLink the TabView bar is still displayed. I'd like it to disappear whenever I click on any NavigationLink.
struct MainView: View {
#State private var tabSelection = 0
var body: some View {
TabView(selection: $tabSelection) {
FirstView()
.tabItem {
Text("1")
}
.tag(0)
SecondView()
.tabItem {
Text("2")
}
.tag(1)
}
}
}
struct FirstView: View {
var body: some View {
NavigationView {
NavigationLink(destination: FirstChildView()) { // How can I open FirstViewChild with the TabView bar hidden?
Text("Go to...")
}
.navigationBarTitle("FirstTitle", displayMode: .inline)
}
}
}
I found a solution to put a TabView inside a NavigationView, so then after I click on a NavigationLink the TabView bar is hidden. But this messes up NavigationBarTitles for Tab items.
struct MainView: View {
#State private var tabSelection = 0
var body: some View {
NavigationView {
TabView(selection: $tabSelection) {
...
}
}
}
}
struct FirstView: View {
var body: some View {
NavigationView {
NavigationLink(destination: FirstChildView()) {
Text("Go to...")
}
.navigationBarTitle("FirstTitle", displayMode: .inline) // This will not work now
}
}
}
With this solution the only way to have different NavigationTabBars per TabView item, is to use nested NavigationViews. Maybe there is a way to implement nested NavigationViews correctly? (As far as I know there should be only one NavigationView in Navigation hierarchy).
How can I hide TabView bar inside NavigationLink views correctly in SwiftUI?
I really enjoyed the solutions posted above, but I don't like the fact that the TabBar is not hiding according to the view transition.
In practice, when you swipe left to navigate back when using tabBar.isHidden, the result is not acceptable.
I decided to give up the native SwiftUI TabView and code my own.
The result is more beautiful in the UI:
Here is the code used to reach this result:
First, define some views:
struct FirstView: View {
var body: some View {
NavigationView {
VStack {
Text("First View")
.font(.headline)
}
.navigationTitle("First title")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.yellow)
}
}
}
struct SecondView: View {
var body: some View {
VStack {
NavigationLink(destination: ThirdView()) {
Text("Second View, tap to navigate")
.font(.headline)
}
}
.navigationTitle("Second title")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.orange)
}
}
struct ThirdView: View {
var body: some View {
VStack {
Text("Third View with tabBar hidden")
.font(.headline)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.red.edgesIgnoringSafeArea(.bottom))
}
}
Then, create the TabBarView (which will be the root view used in your app):
struct TabBarView: View {
enum Tab: Int {
case first, second
}
#State private var selectedTab = Tab.first
var body: some View {
VStack(spacing: 0) {
ZStack {
if selectedTab == .first {
FirstView()
}
else if selectedTab == .second {
NavigationView {
VStack(spacing: 0) {
SecondView()
tabBarView
}
}
}
}
.animation(nil)
if selectedTab != .second {
tabBarView
}
}
}
var tabBarView: some View {
VStack(spacing: 0) {
Divider()
HStack(spacing: 20) {
tabBarItem(.first, title: "First", icon: "hare", selectedIcon: "hare.fill")
tabBarItem(.second, title: "Second", icon: "tortoise", selectedIcon: "tortoise.fill")
}
.padding(.top, 8)
}
.frame(height: 50)
.background(Color.white.edgesIgnoringSafeArea(.all))
}
func tabBarItem(_ tab: Tab, title: String, icon: String, selectedIcon: String) -> some View {
ZStack(alignment: .topTrailing) {
VStack(spacing: 3) {
VStack {
Image(systemName: (selectedTab == tab ? selectedIcon : icon))
.font(.system(size: 24))
.foregroundColor(selectedTab == tab ? .primary : .black)
}
.frame(width: 55, height: 28)
Text(title)
.font(.system(size: 11))
.foregroundColor(selectedTab == tab ? .primary : .black)
}
}
.frame(width: 65, height: 42)
.onTapGesture {
selectedTab = tab
}
}
}
This solution also allows a lot of customization in the TabBar.
You can add some notifications badges, for example.
If we talk about standard TabView, the possible workaround solution can be based on TabBarAccessor from my answer on Programmatically detect Tab Bar or TabView height in SwiftUI
Here is a required modification in tab item holding NavigationView. Tested with Xcode 11.4 / iOS 13.4
struct FirstTabView: View {
#State private var tabBar: UITabBar! = nil
var body: some View {
NavigationView {
NavigationLink(destination:
FirstChildView()
.onAppear { self.tabBar.isHidden = true } // !!
.onDisappear { self.tabBar.isHidden = false } // !!
) {
Text("Go to...")
}
.navigationBarTitle("FirstTitle", displayMode: .inline)
}
.background(TabBarAccessor { tabbar in // << here !!
self.tabBar = tabbar
})
}
}
Note: or course if FirstTabView should be reusable and can be instantiated standalone, then tabBar property inside should be made optional and handle ansbsent tabBar explicitly.
Thanks to another Asperi's answer I was able to find a solution which does not break animations and looks natural.
struct ContentView: View {
#State private var tabSelection = 1
var body: some View {
NavigationView {
TabView(selection: $tabSelection) {
FirstView()
.tabItem {
Text("1")
}
.tag(1)
SecondView()
.tabItem {
Text("2")
}
.tag(2)
}
// global, for all child views
.navigationBarTitle(Text(navigationBarTitle), displayMode: .inline)
.navigationBarHidden(navigationBarHidden)
.navigationBarItems(leading: navigationBarLeadingItems, trailing: navigationBarTrailingItems)
}
}
}
struct FirstView: View {
var body: some View {
NavigationLink(destination: Text("Some detail link")) {
Text("Go to...")
}
}
}
struct SecondView: View {
var body: some View {
Text("We are in the SecondView")
}
}
Compute navigationBarTitle and navigationBarItems dynamically:
private extension ContentView {
var navigationBarTitle: String {
tabSelection == 1 ? "FirstView" : "SecondView"
}
var navigationBarHidden: Bool {
tabSelection == 3
}
#ViewBuilder
var navigationBarLeadingItems: some View {
if tabSelection == 1 {
Text("+")
}
}
#ViewBuilder
var navigationBarTrailingItems: some View {
if tabSelection == 1 {
Text("-")
}
}
}
How about,
struct TabSelectionView: View {
#State private var currentTab: Tab = .Scan
private enum Tab: String {
case Scan, Validate, Settings
}
var body: some View {
TabView(selection: $currentTab){
ScanView()
.tabItem {
Label(Tab.Scan.rawValue, systemImage: "square.and.pencil")
}
.tag(Tab.Scan)
ValidateView()
.tabItem {
Label(Tab.Validate.rawValue, systemImage: "list.dash")
}
.tag(Tab.Validate)
SettingsView()
.tabItem {
Label(Tab.Settings.rawValue, systemImage: "list.dash")
}
.tag(Tab.Settings)
}
.navigationBarTitle(Text(currentTab.rawValue), displayMode: .inline)
}
}
I also faced this problem. I don't want to rewrite, but the solution is in my github. I wrote everything in detail there
https://github.com/BrotskyS/AdvancedNavigationWithTabView
P.S: I have no reputation to write comments. Hikeland's solution is not bad. But you do not save the State of the page. If you have a ScrollView, it will reset to zero every time when you change tab
Also you can create very similar custom navBar for views in TabView
struct CustomNavBarView<Content>: View where Content: View {
var title: String = ""
let content: Content
init(title: String, #ViewBuilder content: () -> Content) {
self.title = title
self.content = content()
}
var body: some View {
content
.safeAreaInset(edge: .top, content: {
HStack{
Spacer()
Text(title)
.fontWeight(.semibold)
Spacer()
}
.padding(.bottom, 10)
.frame(height: 40)
.frame(maxWidth: .infinity)
.background(.ultraThinMaterial)
.overlay {
Divider()
.frame(maxHeight: .infinity, alignment: .bottom)
}
})
}
}
CustomNavBarView(title: "Create ad"){
ZStack{
NavigationLink(destination: SetPinMapView(currentRegion: $vm.region, region: vm.region), isActive: $vm.showFullMap) {
Color.clear
}
Color("Background").ignoresSafeArea()
content
}
}

SwiftUI How to push to next screen when tapping on Button

I can navigate to next screen by using NavigationButton (push) or present with PresentationButton (present) but i want to push when i tap on Buttton()
Button(action: {
// move to next screen
}) {
Text("See More")
}
is there a way to do it?
You can do using NavigationLink
Note: Please try in real device. in simulator sometimes not work properly.
struct MasterView: View {
#State var selection: Int? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailsView(), tag: 1, selection: $selection) {
Button("Press me") {
self.selection = 1
}
}
}
}
}
}
struct DetailsView: View {
#Environment(\.presentationMode) var presentation
var body: some View {
Group {
Button("Go Back") {
self.presentation.wrappedValue.dismiss()
}
}
}
}
As you can see to display the new view, add the NavigationLink with isActive: $pushView using <.hidden()> to hide the navigation "arrow".
Next add Text("See More") with tapGesture to make the text respond to taps. The variable pushView will change (false => true) when you click "See More" text.
import SwiftUI
struct ContentView: View {
#State var pushView = false
var body: some View {
NavigationView {
List {
HStack{
Text("test")
Spacer()
NavigationLink(destination: NewView(), isActive: $pushView) {
Text("")
}.hidden()
.navigationBarTitle(self.pushView ? "New view" : "default view")
Text("See More")
.padding(.trailing)
.foregroundColor(Color.blue)
.onTapGesture {
self.pushView.toggle()
}
}
}
}
}
}
struct NewView: View {
var body: some View {
Text("New View")
}
}
ContentView picture
NewView picture
To tap on button and navigate to next screen,You can use NavigationLink like below
NavigationView{
NavigationLink(destination: SecondView()) {
Text("Login")
.padding(.all, 5)
.frame(minWidth: 0, maxWidth: .infinity,maxHeight: 45, alignment: .center)
.foregroundColor(Color.white)
}
}
You can use NavigationLink to implement this:
struct DetailsView: View {
var body: some View {
VStack {
Text("Hello world")
}
}
}
struct ContentView: View {
#State var selection: Int? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailsView(), tag: 1, selection: $selection) {
Button("Press me") {
self.selection = 1
}
}
}
}
}
}

Resources