SwiftUI list multiple custom row edit option - ios

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())

Related

Dismiss Tab View from a Child Navigation View and go back to RootView SwiftUI

My Navigation Flow:
Here, my View A to View G is under one Navigation View.
NavigationView {
ViewA()
}
And from View D & View G I am moving to my TabView H by modal, like this:
Button(action: {
isPresented.toggle()
}, label: {
Text("GO!")
})
.fullScreenCover(isPresented: $isPresented) {
TabbarView()
}
In my Tab View all the views have their own Navigation View, like this:
TabView(selection: $tabbarViewModel.tabSelection) {
NavigationView {
HomeView()
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Text("Home")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "house")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Home")
}
.tag(0)
NavigationView {
CartView()
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Text("Cart")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "cart")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Cart")
}
.tag(1)
NavigationView {
ProductView()
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Text("Product")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "seal")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Product")
}
.tag(2)
NavigationView {
ProfileView()
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Text("Profile")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "person")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Profile")
}
.tag(3)
}
.accentColor(Color("AppsDefaultColor"))
Now I want to go back to viewA, say from Home View by pressing the Sign Out button. I tried this, just to see if it takes me back to previous view, but it doesn't work.
struct HomeView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Dismiss")
})
}
}
So how can I dismiss the tabview and go back to my Root view A?
Finally I have managed to achieve this. To roll back to the Root view I used this:
NavigationLink(destination: <#T##_#>, tag: <#T##Hashable#>, selection: <#T##Binding<Hashable?>#>, label: <#T##() -> _#>)
And to dismiss the presented view, I used this:
UIApplication.shared.windows.first?.rootViewController?.dismiss(animated: true, completion: nil)
To be honest it's quite simple. All we need to make NavigationLink selection which is selectedItem in this case to nil & dismiss the modal throughout the project. All of these have done inside of a tab bar view model class with the help of #EnvironmentObject
First create the TabbarViewModel: ObservableObject:
import Foundation
import SwiftUI
class TabbarViewModel: ObservableObject {
#Published var tabSelection: Int = 0
#Published var selectedItem: String? = nil
func gotoRootView() {
withAnimation {
UIApplication.shared.windows.first?.rootViewController?.dismiss(animated: true, completion: nil)
selectedItem = nil
}
}
}
Now, let's create the ViewA which is AuthListView:
import SwiftUI
struct CellItem: Identifiable {
var id = UUID()
let title: String
let image: String
let destination: AnyView
}
struct AuthListView: View {
var body: some View {
AuthListContentView()
.navigationBarHidden(true)
}
}
struct AuthListContentView: View {
#State private var cellList: [CellItem] = [
CellItem(title: "Icon", image: "", destination: AnyView(EmptyView())),
CellItem(title: "Phone", image: "Phone", destination: AnyView(PhoneView())),
CellItem(title: "Email", image: "Email", destination: AnyView(SignInView())),
CellItem(title: "Google", image: "Google", destination: AnyView(GoogleView())),
CellItem(title: "Facebook", image: "Facebook", destination: AnyView(FacebookView())),
CellItem(title: "Twitter", image: "Twitter", destination: AnyView(TwitterView()))]
var body: some View {
List(cellList, id: \.id) { item in
if item.title == "Icon" {
IconImageView()
} else {
AuthListCell(cellItem: item)
}
}
}
}
struct IconImageView: View {
var body: some View {
VStack {
Image("firebase")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 120, height: 120, alignment: .center)
}
.frame(maxWidth: .infinity, minHeight: 120, alignment: .center)
.padding(.top, 50)
.padding(.bottom, 50)
}
}
This is each cell of auth View:
import SwiftUI
struct AuthListCell: View {
var cellItem: CellItem
#EnvironmentObject var tabbarViewModel: TabbarViewModel
var body: some View {
NavigationLink(
destination: cellItem.destination,
tag: cellItem.title,
selection: $tabbarViewModel.selectedItem) {
cell(cellItem: cellItem)
}
}
}
struct cell: View {
var cellItem: CellItem
var body: some View {
HStack(spacing: 15) {
Image(cellItem.image)
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 28, maxHeight: .infinity, alignment: .center)
Text(cellItem.title)
.foregroundColor(Color("DefaultText"))
.font(.system(size: 17))
Spacer()
}
.padding(.top, 5)
.padding(.bottom, 5)
}
}
Load this view inside of your ContentView under a Navigation View:
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
var body: some View {
NavigationView {
AuthListView()
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
Till now, we can push to ViewB from ViewA. Here, I am only showing the navigation flow for ViewA push> ViewB push> ViewC present> TabView > and then Dismiss TabView from HomeView and go back to root ViewA, cause rest of the other views will follow the same. It also works from a child navigation of any Tab bar views as well. So let's create ViewB(PhoneView) and push toViewC(PINView):
ViewB:
struct PhoneView: View {
var body: some View {
PhoneContentView()
.navigationBarTitle("Phone Number", displayMode: .inline)
}
}
struct PhoneContentView: View {
var body: some View {
NavigationLink(destination: PINView()) {
Text("Go")
}
}
}
ViewC:
struct PINView: View {
var body: some View {
PINContentView()
.navigationBarTitle("PIN", displayMode: .inline)
}
}
struct PINContentView: View {
#State private var isPresented = false
var body: some View {
Button(action: {
isPresented.toggle()
}, label: {
Text("Sign In")
})
.fullScreenCover(isPresented: $isPresented) {
TabbarView()
}
}
}
Till now we have presented the tab view from previous ViewC. This is our tab view:
import SwiftUI
struct TabbarView: View {
#EnvironmentObject var tabbarViewModel: TabbarViewModel
var body: some View {
TabView(selection: $tabbarViewModel.tabSelection) {
NavigationView {
HomeView().navigationBarTitle("Home", displayMode: .inline)
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "house")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Home")
}
.tag(0)
NavigationView {
CartView().navigationBarTitle("Cart", displayMode: .inline)
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "cart")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Cart")
}
.tag(1)
NavigationView {
ProductView().navigationBarTitle("Product", displayMode: .inline)
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image("product")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Product")
}
.tag(2)
NavigationView {
ProfileView().navigationBarTitle("Profile", displayMode: .inline)
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "person")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Profile")
}
.tag(3)
}
.accentColor(Color("AppsDefaultColor"))
}
}
Now, If I want to dismiss the presented tab view & go back to root view by pressing sign out button from my HomeView, all I have to do is call tabbarViewModel.gotoRootView() like this:
struct HomeView: View {
#EnvironmentObject var tabbarViewModel: TabbarViewModel
var body: some View {
Button(action: {
tabbarViewModel.gotoRootView()
}, label: {
Text("Sign Out")
})
}
}
I can dismiss the tab view and go to the root view from a child view of my HomeView as well. Let's go to a child view from the HomeView:
struct HomeView: View {
var body: some View {
NavigationLink(destination: HomeDetailsView()) {
Text("Go")
}
}
}
This is the HomedetailsView` and by following the same call I can accomplish the same result like this:
struct HomeDetailsView: View {
var body: some View {
HomeDetailsContentView()
.navigationBarTitle("Home Details", displayMode: .inline)
}
}
struct HomeDetailsContentView: View {
#EnvironmentObject var tabbarViewModel: TabbarViewModel
var body: some View {
Button(action: {
tabbarViewModel.gotoRootView()
}, label: {
Text("Dismiss")
})
}
}
By this you can dismiss tab view and go to the root view from any view of your project. :)

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

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.

SwiftUI: How do you dismiss a sheet and launch a different sheet from ContentView?

For my app, I have a welcome screen that intro's what the app does and allows the user to create their first item. When the user clicks the button I'd like to dismiss the 'welcomeScreen' sheet and and then launch the 'newRemindr' sheet.
I tried to achieve this by creating an observable object with an 'addNewTrigger' boolean set to false. When I click the Add New Reminder button on the welcomeScreen, the button's action causes the welcomeScreen to dismiss and toggles the 'addNewTrigger' boolean to True. (I've verified this is working with Print Statements). However content view is listening to that same observed object to launch the 'newRemindr' sheet but that action doesn't seem to be working.
Can somebody please take a look at the code and see where I am going wrong? Or suggest an alternative that can provide the type of functionality.
I really appreciate all the help. Thanks!
Code Below...
welcomeScreen:
import SwiftUI
import Combine
struct welcomeScreen: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
#ObservedObject var addNewReminder = showAddScreen()
var body: some View {
NavigationView {
ZStack (alignment: .center) {
LinearGradient(gradient: Gradient(colors: [Color.white, Color.white, Color.gray]), startPoint: .top, endPoint: .bottom)
.edgesIgnoringSafeArea(.all)
Image("Ellipse2")
.offset(y: -475)
VStack {
Spacer()
Text("Welcome to")
.foregroundColor(.white)
.fontWeight(.bold)
Image("RemindrLogoWhite")
Spacer()
Text("What is remindr?")
.font(.title)
.fontWeight(.bold)
.padding(.bottom, 25)
Text("Remindr is a simple app designed to help you schedule random reminders with the goal of clearing your mind.\n\nRemind yourself to check in with your body, set up positive affirmations, set your intentions; Whatever it is, the power is up to you.")
.padding(.horizontal, 25)
.padding(.bottom, 25)
Text("Click below to get started:")
.fontWeight(.bold)
// Add New Reminder Button
Button(action: {
self.mode.wrappedValue.dismiss()
print("Add Reminder Button from Welcome Screen is Tapped")
self.addNewReminder.addNewTrigger.toggle()
print("var addNewTrigger has been changed to \(self.addNewReminder.addNewTrigger)")
}) {
Image("addButton")
.renderingMode(.original)
}.padding(.bottom, 25)
Spacer()
} .frame(maxWidth: UIScreen.main.bounds.width,
maxHeight: UIScreen.main.bounds.height)
}
.navigationBarTitle(Text(""), displayMode: .automatic)
.navigationBarItems(trailing: Button(action: {
self.mode.wrappedValue.dismiss()
}, label: {
Image(systemName: "xmark")
.foregroundColor(.white)
}))
}
}
}
ContentView:
import SwiftUI
import CoreData
class showAddScreen: ObservableObject {
#Published var addNewTrigger = false
}
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: ReminderEntity.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \ReminderEntity.dateCreated, ascending: false)])
var reminder: FetchedResults<ReminderEntity>
// Sheet Control
#ObservedObject var addNewReminder = showAddScreen()
//#State private var showingAddScreen = false
#State var showWelcomeScreen = false
let emojiList = EmojiList()
//Toggle Control
#State var notifyOn = true
// Save Items Function
func saveItems() {
do {
try moc.save()
} catch {
print(error)
}
}
// Delete Item Function
func deleteItem(indexSet: IndexSet) {
let source = indexSet.first!
let listItem = reminder[source]
moc.delete(listItem)
}
// View Controller
var body: some View {
VStack {
NavigationView {
ZStack (alignment: .top) {
// List View
List {
ForEach(reminder, id: \.self) { notification in
NavigationLink(destination: editRemindr(reminder: notification,
notifyOn: notification.notifyOn,
emojiChoice: Int(notification.emojiChoice),
notification: notification.notification ?? "unknown",
notes: notification.notes ?? "unknown")) {
// Text within List View
HStack {
// MARK: TODO
//Toggle("NotifyOn", isOn: self.$notifyOn)
// .labelsHidden() // Hides the label/title
Text("\(self.emojiList.emojis[Int(notification.emojiChoice)]) \(notification.notification!)")
}
}
}
.onDelete(perform: deleteItem)
}.lineLimit(1)
// Navigation Items
.navigationBarTitle("", displayMode: .inline)
.navigationBarItems(
leading:
HStack {
Button(action: {
self.showWelcomeScreen.toggle()
}) {
Image(systemName: "info.circle.fill")
.font(.system(size: 24, weight: .regular))
}.foregroundColor(.gray)
// Positioning Remindr Logo on Navigation
Image("remindrLogoSmall")
.resizable()
.aspectRatio(contentMode: .fit)
//.frame(width: 60, height: 60, alignment: .center)
.padding(.leading, 83)
.padding(.top, -10)
},
// Global Settings Navigation Item
trailing: NavigationLink(destination: globalSettings()){
Image("settings")
.font(Font.title.weight(.ultraLight))
}.foregroundColor(.gray)
)
// Add New Reminder Button
VStack {
Spacer()
Button(action: { self.addNewReminder.addNewTrigger.toggle()
}) {
Image("addButton")
.renderingMode(.original)
}
.sheet(isPresented: $addNewReminder.addNewTrigger) {
newRemindr().environment(\.managedObjectContext, self.moc)
}
}
}
} .sheet(isPresented: $showWelcomeScreen) {
welcomeScreen()
}
}
}
}
First what I see is you use different observable objects in both views, but should use same, so changes made in one view be available for second view as well.
Se here is a way to solve this
struct welcomeScreen: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
#ObservedObject var addNewReminder: showAddScreen // << declare to be injected
// ... other code
and in ContentView
} .sheet(isPresented: $showWelcomeScreen) {
welcomeScreen(addNewReminder: self.addNewReminder) // << inject !!
}
Alternate: you can remove addNewReminder from welcomeScreen and work with it only in ContentView by activating on welcome sheet dismiss, like
} .sheet(isPresented: $showWelcomeScreen, onDismiss: {
// it is better to show second sheet with delay to give chance
// for first one to animate closing to the end
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.addNewReminder.addNewTrigger.toggle()
}
}
) {
welcomeScreen()
}

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