SwiftUI - Navigation Link pops out on iPhone, but not in Simulator - ios

I have an app that contains several views with NavigationLinks inside.
The main view looks like this, calling a Toolbar view I have created.
struct CountListView: View {
#StateObject var vm = CountListViewModel()
let navigationBar = HomePageNavigationBar()
var body: some View {
NavigationView {
List {
ForEach(vm.count, id: \.uid) { item in
NavigationLink(destination: CountView(count: item)) {
CountListItemView(name: item.name)
}
}
}
.toolbar {
navigationBar.rightSideOfBar()
navigationBar.leftSideOfBar()
}
.navigationBarTitle("Count")
The navigation bar function that is playing up looks like this
func leftSideOfBar() -> some ToolbarContent {
ToolbarItemGroup(placement: .navigationBarLeading) {
NavigationLink(destination: SettingsView()) {
Label("Settings", systemImage: "gear")
}
}
}
And the SettingsView is as follows:
struct SettingsView: View {
var body: some View {
List {
NavigationLink(destination: NameSettingView()) {
Text("Name")
}
.buttonStyle(PlainButtonStyle())
NavigationLink(destination: PrivacyPolicyView()) {
Text("Privacy Policy")
}
.buttonStyle(PlainButtonStyle())
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
When I open the Privacy Policy View on device, the returns to the SettingsView without any user intervention. But this problem doesn't exist in the simulator.

Related

NavigationStack hides the second view's navigation title and toolbar

So I built an app where you can add your personal expenses and it works fine but I added a loginView with faceID and when you login with that, it hides the second view's add and edit button, besides the search bar where you can filter the expenses
Here's the code that worked before i added the loginView
struct MainView: View {
#StateObject var PEviewModel = PEViewModel()
var body: some View {
// NavigationStack {
TabView {
PersonalExpensesView()
.environmentObject(PEviewModel)
.tabItem {
Label("Personal expenses list", systemImage: "dollarsign.circle")
}
// another view
.tabItem {
Label("Graph", systemImage: "chart.bar.fill")
}
}
}
//.navigationBarBackButtonHidden()
}
}
The MainView contains the two views you can navigate in. I added that navigationStack and the navigationBarBackButton() just to delete the back bar button so I could try if it worked (it didn't). PersonalExpensesView has the following (and it worked before the logInUpdate)
var body: some View {
NavigationStack {
VStack {
// all the listing of personal expenses and stuff
.navigationTitle("Personal Expenses")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
EditButton()
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
showingSheet = true
} label: {
Label("Add Book", systemImage: "plus")
}
}
}
}
.searchable(text: $textFilter)
.sheet(isPresented: $showingSheet) {
NewPEView()
.environmentObject(PEviewModel)
}
}
}
}
And here's the code I added later (the logIn code) :
var body: some View {
NavigationStack {
VStack {
Button() {
authenticate()
} label: {
Label("Usar Face ID", systemImage: "faceid")
}
.customButton()
.navigationDestination(isPresented: $unlocked){// authenticate() sets unlocked true
MainView()
}
}
}
So the correct UI (without using login) is the following
correct
And with login: bugged
I tried using a NavigationStack as I showed in the MainView()

How to disable refreshable in nested view which is presented as sheet/fullscreenCover in SwiftUI?

I am using .refreshable to List in Home Screen. When user clicks on any cell item from the List, presenting DetailsView by sheet/fullscreenCover. But, .refreshable is still attached with DetailsView.
How to disable refreshable in nested view (DetailsView) which is presented from Home Screen?
HomeView.swift
struct HomeView: View {
#State private var showDetailsView: Bool = false
var body: some View {
NavigationView {
List(0..<29) { _ in
Text("Hello, world!")
.padding()
.onTapGesture {
showDetailsView = true
}
//.sheet or .fullScreenCover
.fullScreenCover(isPresented: $showDetailsView) {
DetailsView()
}
}
.refreshable {
print("refreshing...")
}
.navigationTitle("Home")
}
}
}
DetailsView.swift
struct DetailsView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
List(0..<29) { _ in
Text("DetailsView...")
.padding()
}
.navigationTitle("DetailsView")
.navigationBarItems(
leading:
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Text("Close")
}
)
}
}
}
}
Move fullScreenCover modifier out of NavigationView (on home), and probably in real app it will be needed to use variant with item fullScreenCover(item:) instead to pass selection.
var body: some View {
NavigationView {
List(0..<29) { _ in
Text("Hello, world!")
.padding()
.onTapGesture {
showDetailsView = true
}
//.sheet or .fullScreenCover
}
.refreshable {
print("refreshing...")
}
.navigationTitle("Home")
}
.fullScreenCover(isPresented: $showDetailsView) { // << here !!
DetailsView()
}
}
Tested with Xcode 13.3 / iOS 15.4

SwiftUI StackNavigationViewStyle issue when rotating iPhone

I want to implement a Settings view which can be opened taping on a gear icon button in the navigation tool bar.
This button opens a SwiftUI sheet with on Ok button to validate settings and close the settings window.
It works well if you use it without rotating the iPhone.
But if you rotate the phone when the settings window is opened, the Ok button does not work anymore and the window stays on screen (even if you rotate back the phone).
In the console, an error appears when I rotate the phone. Here is the message:
[Presentation] Attempt to present <…> on <…> (from <…>) which is already presenting
This issue seems to be linked to StackNavigationViewStyle() modifier I use to not have 2 columns on landscape mode.
If I remove the following line, the bug disappears but the layout is no more the one I want.
.navigationViewStyle(StackNavigationViewStyle())
Here is a sample code I wrote to reproduce the problem:
import SwiftUI
struct ContentView: View {
// Size class
#Environment(\.verticalSizeClass) var sizeClassV
#State private var showGearView: Bool = false
var gearButton: some View {
HStack {
Button(action: {
self.showGearView.toggle()
}) {
Image(systemName: "gear")
.imageScale(.large)
.accessibility(label: Text("Settings"))
}
.sheet(isPresented: self.$showGearView, onDismiss: {
}, content: {
gearView()
})
}
}
var body: some View {
return NavigationView {
// if sizeClassV == .regular {
VStack {
Text("Click on Gear and rotate your iPhone: here is the bug when clicking on Ok: the sheet does not collapse!")
.multilineTextAlignment(.center)
.padding(.all)
}
.padding(.all)
.navigationTitle("Bug")
.navigationBarTitleDisplayMode(.inline)
.toolbar(content: { gearButton })
}
// The bug only happens when adding the StackNavigationViewStyle below
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct gearView: View {
#Environment(\.presentationMode) var presentationMode
var OKButton: some View {
HStack {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("OK")
}
}
}
var body: some View {
return NavigationView {
VStack {
Form {
Section(header: Text("Settings")) {
Text("No Settings")
}
}
}
.navigationTitle(Text("Settings"))
.toolbar(content: {
ToolbarItem(placement: .primaryAction) {
OKButton
}
})
}
}
}

Presenting a modal view sheet from a Sub view

I am trying to present a sheet from a sub view selected from the menu item on the navigation bar but the modal Sheet does does not display. I spent a few days trying to debug but could not pin point the problem.
I am sorry, this is a little confusing and will show a simplified version of the code to reproduce. But in a nutshell, the problem seems to be a sheet view that I have as part of the main view. Removing the sheet code from the main view displays the sheet from the sub view. Unfortunately, I don't have the freedom to change the Mainview.swift
Let me show some code to make it easy to understand....
First, before showing the code, the steps to repeat the problem:
click on the circle with 3 dots in the navigation bar
select the second item (Subview)
click on the "Edit Parameters" button and the EditParameters() view will not display
ContentView.swift (just calls the Mainview()). Included code to copy for reproducing issue :-)
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Mainview()
}
}
}
}
Mainview.swift. This is a simplified version of the actual App which is quite complex and I don't have leeway to change much here unfortunately!
fileprivate enum CurrentView {
case summary, sub
}
enum OptionSheet: Identifiable {
var id: Self {self}
case add
}
struct Mainview: View {
#State private var currentView: CurrentView? = .summary
#State private var showSheet: OptionSheet? = nil
var body: some View {
GeometryReader { g in
content.frame(width: g.size.width, height: g.size.height)
.navigationBarTitle("Main", displayMode: .inline)
}
//Removing the below sheet view will display the sheet from the subview but with this sheet here, it the sheet from subview does not work. This is required as these action items are accessed from the second menu item (circle and arrow) navigation baritem
.sheet(item: $showSheet, content: { mode in
sheetContent(for: mode)
})
.toolbar {
HStack {
trailingBarItems
actionItems
}
}
}
var actionItems: some View {
Menu {
Button(action: {
showSheet = .add
}) {
Label("Add Elements", systemImage: "plus")
}
} label: {
Image(systemName: "cursorarrow.click").resizable()
}
}
var trailingBarItems: some View {
Menu {
Button(action: {currentView = .summary}) {
Label("Summary", systemImage: "list.bullet.rectangle")
}
Button(action: {currentView = .sub}) {
Label("Subview", systemImage: "circle")
}
} label: {
Image(systemName: "ellipsis.circle").resizable()
}
}
#ViewBuilder
func sheetContent(for mode: OptionSheet) -> some View {
switch mode {
case .add:
AddElements()
}
}
#ViewBuilder
var content: some View {
if let currentView = currentView {
switch currentView {
case .summary:
SummaryView()
case .sub:
SubView()
}
}
}
}
Subview.swift. This is the file that contains the button "Edit Parameters" which does not display the sheet. I am trying to display the sheet from this view.
struct SubView: View {
#State private var editParameters: Bool = false
var body: some View {
VStack {
Button(action: {
editParameters.toggle()
}, label: {
HStack {
Image(systemName: "square.and.pencil")
.font(.headline)
Text("Edit Parameters")
.fontWeight(.semibold)
.font(.headline)
}
})
.padding(10)
.foregroundColor(Color.white)
.background(Color(.systemBlue))
.cornerRadius(20)
.sheet(isPresented: $editParameters, content: {
EditParameterView()
})
.padding()
Text("Subview....")
}
.padding()
}
}
EditParameters.swift. This is the view it should display when the Edit Parameters button is pressed
struct EditParameterView: View {
var body: some View {
Text("Edit Parameters...")
}
}
Summaryview.swift. Nothing special here. just including for completeness
struct SummaryView: View {
var body: some View {
Text("Summary View")
}
}
In SwiftUI, you can't have 2 .sheet() modifiers on the same hierarchy. Here, the first .sheet() modifier is on one of the parent views to the second .sheet(). The easy solution is to move one of the .sheets() so it's own hierarchy.
You could either use ZStacks:
var body: some View {
ZStack {
GeometryReader { g in
content.frame(width: g.size.width, height: g.size.height)
.navigationBarTitle("Main", displayMode: .inline)
}
ZStack{ }
.sheet(item: $showSheet, content: { mode in
sheetContent(for: mode)
})
}
.toolbar {
HStack {
trailingBarItems
actionItems
}
}
}
or more elegantly:
var body: some View {
GeometryReader { g in
content.frame(width: g.size.width, height: g.size.height)
.navigationBarTitle("Main", displayMode: .inline)
}
.background(
ZStack{ }
.sheet(item: $showSheet, content: { mode in
sheetContent(for: mode)
})
)
.toolbar {
HStack {
trailingBarItems
actionItems
}
}
}

SwiftUI toolbar not showing on a NavigationLink view

I'm trying to show a toolbar on a view that is inside to navigation links. When I navigate to the third view I get the following message:
2020-09-15 23:09:31.097289-0500 CountTime[35018:3542166] [Assert]
displayModeButtonItem is internally managed and not exposed for
DoubleColumn style. Returning an empty, disconnected UIBarButtonItem
to fulfill the non-null contract.
And the toolbar is not shown. This happens only on iPhone, not iPad. I'm using Xcode 12 GM.
Here is the code:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(
destination: SecondView(),
label: {
Text("Navigate")
})
}
}
}
struct SecondView: View {
var body: some View {
ZStack {
NavigationLink(
destination: Text("Destination")
.toolbar {
ToolbarItem(placement: ToolbarItemPlacement.bottomBar) {
Button(action: {
print("sharing")
}) {
Image(systemName: "square.and.arrow.up")
}
}
},
label: {
Text("Navigate")
})
}
}
}
displayModeButtonItem is internally managed and not exposed for
DoubleColumn style
In your case SwiftUI for some reason tries to present a NavigationView in a DoubleColumn style.
A possible solution is to specify the style explicitly:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: SecondView()) {
Text("Navigate")
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
I got the some issue.
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button(action: {
showEditView = true
}, label: {
Text("Edit")
})
}
}
does not work but the deprecated navigationBarItems works.
.navigationBarItems(trailing:
Button(action: {
showEditView = true
}, label: {
Text("Edit")
})
)

Resources