How to popToRoot with the new iOS 16 NavigationStack inside a TabView? - swiftui

I would like to be able to popToRoot from my first tabItem (when im on page2 (from first tabItem) and tap twice on first tabItem when im on second tabItem) without having to use the NavigationViewKit package and only the new NavigationStack, is that possible?
My solution works fine but I would like to get rid of the NavigationViewKit package.
Any good advice\code example will be appreciated.
My code :
Home page
import SwiftUI
import NavigationViewKit
struct Home_V: View {
#State var tabSelected: Int = 0
#State private var tappedTwice: Bool = false
// https://designcode.io/swiftui-handbook-tabbar-to-root-view
var handler: Binding<Int> { Binding(
get: { tabSelected },
set: {
if $0 == tabSelected {
// print("tabSelected == \(tabSelected)")
tappedTwice = true
}
tabSelected = $0
}
)}
// https://github.com/fatbobman/NavigationViewKit
#Environment(\.navigationManager) var nvmanager
var body: some View {
TabView(selection: handler) {
NavigationStack {
Page1()
.onChange(of: tappedTwice, perform: { tappedTwice in
guard tappedTwice else { return }
if tabSelected == 0 {
self.tappedTwice = false
nvmanager.wrappedValue.popToRoot(tag:"Page1", animated: true){}
}
})
}
.tabItem {
Label("_HomeTitle", systemImage: "house")
.environment(\.symbolVariants, tabSelected == 0 ? .fill : .none)
}
.tag(0)
.navigationViewStyle(StackNavigationViewStyle())
NavigationStack {
Page2()
}
.tabItem {
Label("_MessagesTitle", systemImage: "envelope")
.environment(\.symbolVariants, tabSelected == 1 ? .fill : .none)
}
.tag(1)
.navigationViewStyle(StackNavigationViewStyle())
}
}
}
Page1
import SwiftUI
import NavigationViewKit
struct Page1: View {
var body: some View {
VStack {
Text("Page 1")
NavigationLink {
Page2()
} label: {
Text("Go to Page 2")
}
}
.navigationViewManager(for: "Page1", afterBackDo: {print("Back to Page1")})
}
}

I got it!
Here is my test code :
import SwiftUI
class NavigationCoordinator: ObservableObject {
#Published var path = NavigationPath()
func popToRoot() {
path.removeLast(path.count)
}
}
struct Test_PopToRoot_NavigationStack: View {
#State private var tabSelected: Int = 0
#State private var tappedTwice: Bool = false
#StateObject var navigationCoordinator = NavigationCoordinator()
#StateObject var navigationCoordinator2 = NavigationCoordinator()
// https://designcode.io/swiftui-handbook-tabbar-to-root-view
var handler: Binding<Int> { Binding(
get: { tabSelected },
set: {
if $0 == tabSelected {
// print("tabSelected == \(tabSelected)")
tappedTwice = true
}
tabSelected = $0
}
)}
var body: some View {
TabView(selection: handler) {
NavigationStack(path: $navigationCoordinator.path) {
VStack {
NavigationLink(value: 1) {
Test_PopToRoot_Tabview1()
.foregroundColor(.black)
.onChange(of: tappedTwice, perform: { tappedTwice in
guard tappedTwice else { return }
if tabSelected == 0 {
self.tappedTwice = false
print("Home tapped twice!!!")
navigationCoordinator.popToRoot()
}
})
}
}
}
.environmentObject(navigationCoordinator)
.tabItem {
Label("_HomeTitle", systemImage: "house")
.environment(\.symbolVariants, tabSelected == 0 ? .fill : .none)
}
.tag(0)
.navigationViewStyle(StackNavigationViewStyle())
NavigationStack(path: $navigationCoordinator2.path) {
VStack {
NavigationLink(value: 1) {
Test_PopToRoot_Tabview2()
.foregroundColor(.black)
.onChange(of: tappedTwice, perform: { tappedTwice in
guard tappedTwice else { return }
if tabSelected == 1 {
self.tappedTwice = false
print("2nd Tab tapped twice!!!")
navigationCoordinator2.popToRoot()
}
})
}
}
}
.environmentObject(navigationCoordinator2)
.tabItem {
Label("_MessagesTitle", systemImage: "envelope")
.environment(\.symbolVariants, tabSelected == 1 ? .fill : .none)
}
.tag(1)
.navigationViewStyle(StackNavigationViewStyle())
}
}
}
struct Test_PopToRoot_Tabview1: View {
var body: some View {
VStack {
NavigationLink(value: 2) {
Text("Go To Page2")
.foregroundColor(.black)
}
}
.navigationDestination(for: Int.self) { i in
Test_PopToRoot_Page2()
}
.navigationTitle(Text("Tabview1"))
}
}

Related

How to close Half sheet in this situation?

I want to close the half sheet and back to the root view In swiftUI when with navigationLink we go to the another view. dismiss() doesn't work and turn us back to the previous view not the root view.
struct ContentView: View {
#State var showFirstSheetView = false
var body: some View {
NavigationView {
VStack {
Text("half sheet")
.onTapGesture {
showFirstSheetView.toggle()
}
}
.navigationTitle("Root view")
.sheet(isPresented: $showFirstSheetView) {
halfSheet()
}
}
}
}
struct halfSheet : View {
#State var showSecondView = false
var body: some View {
NavigationView {
VStack {
}
.background(
NavigationLink(isActive: $showSecondView, destination: {
SecondView()
}, label: {
EmptyView()
})
)
.navigationTitle("First view")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems( trailing: Button(action: {
showSecondView.toggle()
}) {
Text("Next")
}
)
}
}
}
struct SecondView : View {
#Environment(\.dismiss) var dismiss
var body: some View {
VStack {
}
.navigationTitle("Second view")
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
dismiss()
}) {
HStack(spacing : 5) {
Image(systemName: "chevron.backward")
Text("Back")
}
}, trailing: Button(action: {
// How back to the root View?
// below code works but not compatible with ios 15 and gives a warning
// UIApplication.shared.windows.first?.rootViewController?.dismiss(animated: true)
}) {
Text("Done")
})
}
}

Disable item in TabView SwiftUI

How Can I set an item to disabled (not clickable) but visible in my tabView ?
TabView(selection: $selectedTab) {
Settings()
.tabItem {
Image(systemName: "gearshape.fill")
Text("Settings")
}.tag(1)
.disabled(true) // Not Working
I just create a way to do what you want fully supported and customisable!
test with Xcode Version 12.1, iOS 14.1, Here goes:
import SwiftUI
struct ContentView: View {
#State private var selection = 0
#State private var exSelection = 0
private var disableThis = 2
var body: some View
{
TabView(selection: $selection)
{
viewFinder(selectedIndex: selection == disableThis ? $exSelection : $selection)
.tabItem { Image(systemName: "1.circle") }
.tag(0)
viewFinder(selectedIndex: selection == disableThis ? $exSelection : $selection)
.tabItem { Image(systemName: "2.circle") }
.tag(1)
viewFinder(selectedIndex: selection == disableThis ? $exSelection : $selection)
.tabItem { Image(systemName: "3.circle") }
.tag(2)
viewFinder(selectedIndex: selection == disableThis ? $exSelection : $selection)
.tabItem { Image(systemName: "4.circle") }
.tag(3)
}
.onAppear()
{
UITabBar.appearance().barTintColor = .white
}
.accentColor(selection == disableThis ? Color.gray : Color.red)
.onChange(of: selection) { _ in
if selection != disableThis { exSelection = selection } else { selection = exSelection }
}
}
}
struct viewFinder: View
{
#Binding var selectedIndex: Int
var body: some View {
return Group
{
if selectedIndex == 0
{
FirstView()
}
else if selectedIndex == 1
{
SecondView()
}
else if selectedIndex == 2
{
ThirdView()
}
else if selectedIndex == 3
{
FourthView()
}
else
{
EmptyView()
}
}
}
}
struct FirstView: View { var body: some View {Text("FirstView")}}
struct SecondView: View { var body: some View {Text("SecondView")}}
struct ThirdView: View { var body: some View {Text("ThirdView")}}
struct FourthView: View { var body: some View {Text("FourthView")}}
There is not direct SwiftUI instrument for this now (SwiftUI 2.0), so find below possible approach based on TabBarAccessor from my another answer https://stackoverflow.com/a/59972635/12299030.
Tested with Xcode 12.1 / iOS 14.1 (note - tint color changed just for demo because disabled item is grey and invisible on grey tabbar)
struct TestTabBar: View {
init() {
UITabBar.appearance().unselectedItemTintColor = UIColor.green
}
#State private var selection = 0
var body: some View {
TabView(selection: $selection) {
Text("First View")
.background(TabBarAccessor { tabBar in
tabBar.items?.last?.isEnabled = false // << here !!
})
.tabItem { Image(systemName: "1.circle") }
.tag(0)
Text("Second View")
.tabItem { Image(systemName: "2.circle") }
.tag(1)
}
}
}

SwiftUI multiple popovers in a List

I defined 2 popovers and one sheet in the View Line().
Using this view in a VStack, everything works fine.
Using it inside a List, the wrong popovers /sheets are displayed when the corresponding text or Button is tapped.
What's going wrong here?
struct ContentView: View {
var body: some View {
VStack {
Line()
List {
Line()
Line()
Line()
}
}
}
}
struct Line: View {
#State private var showPopup1 = false
#State private var showPopup2 = false
#State private var showSheet2 = false
var body: some View {
VStack {
Text("popover 1")
.onTapGesture { self.showPopup1 = true}
.popover(isPresented: $showPopup1, arrowEdge: .trailing )
{ Popover1(showSheet: self.$showPopup1) }
.background(Color.red)
Text("popover 2")
.onTapGesture { self.showPopup2 = true }
.popover(isPresented: $showPopup2, arrowEdge: .trailing )
{ Popover2(showSheet: self.$showPopup2) }
.background(Color.yellow)
Button("Sheet2"){self.showSheet2 = true}
.sheet(isPresented: self.$showSheet2, content: { Sheet2()})
}
}
}
struct Popover1: View {
#Binding var showSheet: Bool
var body: some View {
VStack {
Text("Poppver 1 \(self.showSheet ? "T" : "F")")
Button("Cancel"){ self.showSheet = false }
}
}
}
struct Popover2: View {
#Binding var showSheet: Bool
var body: some View {
VStack {
Text("Poppver 2")
Button("Cancel"){ self.showSheet = false }
}
}
}
struct Sheet2: View {
#Environment(\.presentationMode) var presentation
var body: some View {
VStack {
Text("Sheet 2")
Button("Cancel"){ self.presentation.wrappedValue.dismiss() }
}
}
}
Just don't use Button for .sheet. List detects buttons in row and activate entire row (not sure about bug, let it be as designed). So using only and for everywhere in sub-elements gestures, makes your code work.
Tested with Xcode 11.2 / iOS 13.2
var body: some View {
VStack {
Text("popover 1")
.onTapGesture { self.showPopup1 = true}
.popover(isPresented: $showPopup1, arrowEdge: .trailing )
{ Popover1(showSheet: self.$showPopup1) }
.background(Color.red)
Text("popover 2")
.onTapGesture { self.showPopup2 = true }
.popover(isPresented: $showPopup2, arrowEdge: .trailing )
{ Popover2(showSheet: self.$showPopup2) }
.background(Color.yellow)
Text("Sheet2") // << here !!!
.onTapGesture {self.showSheet2 = true} // << here !!!
.sheet(isPresented: self.$showSheet2, content: { Sheet2()})
}
}

SwiftUI - Button Action to Alert and take action

I am trying to send alert based on a condition, but the Navigation link is executing regardless of the condition. I was hoping for an intercept.
Goal:
If condition is not me then do not launch new view
New View is launching and then alert.
I am sure my code is incorrect, but I am unsure how I should achieve this
Thanks in advance.
var body: some View {
NavigationView {
VStack {
Button(action: {}) {
//NavigationLink(destination: secondView()) {
NavigationLink(destination: checkState()) {
Text("Add to Cart")
}.padding()
.font(.system(size: 14))
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(6)
}
}
}.padding()
} // End of the GetOrder Struct
struct GetdOrderView_Previews: PreviewProvider {
static var previews: some View {
GetdOrderView()
}
}
}
struct checkState: View {
#ObservedObject var calcCheck = MealOrder()
#State var showingAlert = false
#State var myToggle = false
var body: some View {
NavigationView {
VStack {
Button(action: {
//Enter Action here
if self.myToggle == true {
self.showingAlert = true
} else {
self.showingAlert = true
}
}) {
Text("This is a test")
}.padding()
.font(.system(size: 14))
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(6)
//Insert Alerts
.alert(isPresented: $showingAlert) {
if self.myToggle {
return Alert(title: Text("Showing Message"), message: Text("Cart is valid"), dismissButton: .default(Text("OK")))
} else {
return Alert(title: Text("Showing Alert"), message: Text("Cart Empty"), dismissButton: .default(Text("Cancel")))
}
}
}
}
}
}
struct secondView: View {
var body: some View {
VStack {
Text("This is the second test")
}
}
}
Try the following approach
#State var activateLink = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: checkState(), isActive: $activateLink) {
EmptyView()
}
Button(action: {
if _YOUR_CONDITION_HERE_ {
self.activateLink = true
}
}) {
Text("Add to Cart")
.padding()
.font(.system(size: 14))
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(6)
}
}
.onAppear { self.activateLink = false }
}.padding()
}// End of the GetOrder Struct

How to delete multiple rows from List in SwiftUI?

I took an example from this question: How does one enable selections in SwiftUI's List and edited the code to be able to delete rows one by one. But I don't know how to delete multiple rows from list.
Could you help me, please?
var demoData = ["Phil Swanson", "Karen Gibbons", "Grant Kilman", "Wanda Green"]
struct ContentView : View {
#State var selectKeeper = Set<String>()
var body: some View {
NavigationView {
List(selection: $selectKeeper){
ForEach(demoData, id: \.self) { name in
Text(name)
}
.onDelete(perform: delete)
}
.navigationBarItems(trailing: EditButton())
.navigationBarTitle(Text("Selection Demo \(selectKeeper.count)"))
}
}
func delete(at offsets: IndexSet) {
demoData.remove(atOffsets: offsets)
}
}
solution from SwiftUI how to perform action when EditMode changes?
struct Item: Identifiable {
let id = UUID()
let title: String
static var i = 0
init() {
self.title = "\(Item.i)"
Item.i += 1
}
}
struct ContentView: View {
#State var editMode: EditMode = .inactive
#State var selection = Set<UUID>()
#State var items = [Item(), Item(), Item()]
var body: some View {
NavigationView {
List(selection: $selection) {
ForEach(items) { item in
Text(item.title)
}
}
.navigationBarTitle(Text("Demo"))
.navigationBarItems(
leading: editButton,
trailing: addDelButton
)
.environment(\.editMode, self.$editMode)
}
}
private var editButton: some View {
Button(action: {
self.editMode.toggle()
self.selection = Set<UUID>()
}) {
Text(self.editMode.title)
}
}
private var addDelButton: some View {
if editMode == .inactive {
return Button(action: addItem) {
Image(systemName: "plus")
}
} else {
return Button(action: deleteItems) {
Image(systemName: "trash")
}
}
}
private func addItem() {
items.append(Item())
}
private func deleteItems() {
for id in selection {
if let index = items.lastIndex(where: { $0.id == id }) {
items.remove(at: index)
}
}
selection = Set<UUID>()
}
}
extension EditMode {
var title: String {
self == .active ? "Done" : "Edit"
}
mutating func toggle() {
self = self == .active ? .inactive : .active
}
}