SwiftUI NavigationView content only visible in iPad sidebar - ios

My app is working as expected across all iPhone models, but when running on iPad I notice that the my application content, which is wrapped within a NavigationView, only displays in the iPad's sidebar, and only after tapping the 'Back` toolbar button.
var body: some View {
NavigationView{
ZStack{
...
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
VStack {
Text("Add Light to Your Journey")
.font(Font.custom("EduTASBeginner-Regular", size: 24))
...
}
}
I found a similar question on SO that suggested adding the attribute .navigationViewStyle(.stack), but this did not change the way the app is displayed on iPad:
Note the solution on this similar post also did not resolve the issue.

As noted in the comments on the linked post, the .navigationViewStyle(StackNavigationViewStyle()) must be applied directly to the NavigationView, and not a view contained therein as with .navigationTitle

Related

How to only apply preferred color scheme to one view and not the rest of the views in the view hierarchy in SwiftUI

I am trying to make one view render in dark mode while the rest of my app is in the users chosen color scheme. When I apply .preferredColorScheme(.dark) to the subview, it causes other views to turn dark as well. How can I fix this behavior?
ContentView:
NavigationView {
ZStack {
NavigationLink(isActive: $showingGoalDashboardView) {
TestView(goal: goals.first!)
} label: {
EmptyView()
}
NavigationLink(isActive: $showingCreateGoalView) {
CreateGoalView(showingGoalCreateView: $showingCreateGoalView)
} label: {
EmptyView()
}
LoadingView()
}
}
LoadingView:
LoadingView just contains some UI elements, all wrapped in a ZStack with the property .preferredColorScheme(.dark) applied to it.
The preferredColorScheme works NOT per-view, but for current presentation - which in this case is a current window. See documentation:
Put LoadingView into sheet or popover, or new window, etc, and there dark mode will be applied independently.
Update: well, actually it can still be used View.colorScheme, and it works, but it has been deprecated - just be aware:
LoadingView()
.colorScheme(.dark)
Tested with Xcode 13.4 / iOS 15.5

Swiftui NavigationView + TabView doesn't show navbar item

I have four Views inside a TabView and each of them contains NavigationView with title. However, when the view first shows up, the navigation view does not show as designed.
Even though I have the navigation bar item, the view would always be a blank child view. It is only when I click to another page and then coming back to the navigation view that the view would show normally. What could be the problems?
Attached is the screenshot of the preview page. Thanks in advance.
struct MainContentView: View {
#State private var navSelection = 0
var body: some View {
VStack(alignment: .center){
TabView(selection:$navSelection){
NavigationView{
HomeView()
.navigationBarItems(leading: Text("Title").font(.system(size:24,weight: .heavy)), trailing: Image(systemName: "bell.fill"))
.navigationViewStyle(StackNavigationViewStyle())
}.tag(0)
NavigationView{
ExploreView()
}.tag(1)
Text("Post").tag(2)
Text("Market").tag(3)
Text("Account").tag(4)
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.disabled(true)
MainTabBarView(navSelection: self.$navSelection)
}
}
}
The preview sample
Just a small mistake, apply the style to the navigation view as follows:
NavigationView{
}
.navigationViewStyle(.stack)
What went wrong is some how SwiftUI found a detail view somewhere in the hierarchy and pushed it because normally inside NavigationView there are two views defined but you only defined one. I think .tabViewStyle is what caused it.
Also to set the nav item title we use .navigationTitle("Home")

SwiftUI TextEditor View content is hidden behind Navigation Bar

I'm working on a SwiftUI View with a NavigationBar. The view is very simple, it's a full-page TextEditor:
struct NotesEditingScreen: View {
#State var text: String
var body: some View {
TextEditor(text: $text)
.padding(.horizontal)
.navigationBarTitle("Editing")
}
}
The issue I'm seeing, is that when landing on this screen (via a NavigationLink) the top of the TextEditor is covered up by Navigation Bar:
My desired behavior is that the TextEditor content appears beneath the Navigation Bar, like it appears after you manually scroll to the top to reveal the text.
Is there a solution/workaround to this issue? I was hoping for either some offset, a setting on NavigationBar, or some programmatic scroll behavior that could be done onAppear. Any suggestions welcome.
I think it's an unexpected behavior.
You can try this:
GeometryReader { geo in
ScrollView {
TextEditor(text: $text)
.frame(height: geo.size.height)
}
}

Unable to present ActionSheet via a NavigationBarItem in SwiftUI on an iPad

First, I have looked at a similar question, but it does not address my use case.
Present ActionSheet in SwiftUI on iPad
My issue is that I have a NavigationBarItem in my NavigationView that will toggle an ActionSheet when pressed. This behavior works properly when used on an iPhone.
However, when I use this on an iPad, both buttons on my screen will gray out and nothing happens. Clicking the buttons again will make them active (blue), but again, no sheet is presented.
Finally, if I select the button in the middle of the screen (Show Button), then an ActionSheet is properly presented on an iPad.
I have tested with Xcode 11 & iOS 13.5 and Xcode 12 & iOS 14. There is no change in behavior.
import SwiftUI
struct ContentView: View {
#State private var isButtonSheetPresented = false
#State private var isNavButtonSheetPresented = false
var body: some View {
NavigationView {
Button(action: {
// Works on iPad & iPhone
self.isButtonSheetPresented.toggle()
}) {
Text("Show Button")
}
.actionSheet(isPresented: $isButtonSheetPresented,
content: {
ActionSheet(title: Text("ActionSheet"))
})
.navigationBarTitle(Text("Title"),
displayMode: .inline)
.navigationBarItems(trailing:
Button(action: {
// Works on iPhone, fails on iPad
self.isNavButtonSheetPresented.toggle()
}) {
Text("Show Nav")
}
.actionSheet(isPresented: $isNavButtonSheetPresented,
content: {
ActionSheet(title: Text("ActionSheet"))
})
)
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
Finally, this is how it appears on an iPad when clicking on "Show Nav":
This is a simplified setup for the screen where this issue occurs. I will need to retain the navigation settings shown, but have included them for clarity.
*** UPDATED ***
While it is not possible for the real app behind this, I did remove the .navigationViewStyle(StackNavigationViewStyle()) setting, which did make an ActionSheet appear, although in the wrong spot as seen below.
This also results in bizarre placement for the Button one accessed via "Show Button".
Yes, it is a bug, but probably different - that Apple does not allow to change anchor and direction of shown ActionSheet, because it is shown, but always to the right of originated control on iPad. To prove this it is enough to change location of button in Navigation
Here is example of placing at .leading position. Tested with Xcode 12 / iOS 14
.navigationBarItems(leading:
Button(action: {
// Works on iPhone, fails on iPad
self.isNavButtonSheetPresented.toggle()
}) {
Text("Show Nav")
}
.actionSheet(isPresented: $isNavButtonSheetPresented,
content: {
ActionSheet(title: Text("ActionSheet"))
})
)
Note: SwiftUI 2.0 .toolbar behaves in the same way, ie. has same bug.
This is an old question but if someone is interested in a turnaround that works on iOS 14:
I have two navigation bar trailing buttons inside .toolbar() and they should open action sheets. I placed an invisible "bar" at the top of the view to use it as an anchor:
var body: some View {
VStack {
HStack {
Spacer()
Color.clear.frame(width: 1, height: 1, alignment: .center)
.actionSheet(/*ActionSheet for first button*/)
Spacer().frame(width: 40)
Color.clear.frame(width: 1, height: 1, alignment: .center)
.actionSheet(/*ActionSheet for second button*/)
Spacer().frame(width: 40)
}.frame(height: 1)
}
}
Cons:
There's a tiny bar/extra space at the top, noticeable especially during scrolling (Maybe putting the Stack in the background with a Stack could remove it?).
You might need to adjust the Spacers' width to try and align the ActionSheets to their respective button.
You can't force the action sheet arrows to always point upwards, I tested this on another simulator and the rightmost ActionSheet had its arrow pointing to the right (the 'illusion' that it came from the button was still there)
Here's how it looks

SwiftUI: How can I restrict the tappable area of a view when presenting a modal(actually not modal) view over a main view?

I am developing an app based on a Tabview with three TabItems. Each TabItem is a List and I would be able to show a kind of modal view over those Lists. The problem becomes when I can not call a Sheet as modal view because Sheets are almost full windowed. I need some kind of bottom modal view, so I create a View that I present over a List with higher ZIndex. It seems to work until you click in the tabbar and select another TabItem having deployed the "modal" view. The error is:
[TableView] Warning once only: UITableView was told to layout its
visible cells and other contents without being in the view hierarchy
(the table view or one of its superviews has not been added to a
window). This may cause bugs by forcing views inside the table view to
load and perform layout without accurate information (e.g. table view
bounds, trait collection, layout margins, safe area insets, etc), and
will also cause unnecessary performance overhead due to extra layout
passes.
So, I would like as solution to restrict the tappable area to the "modal" view area. ¿Is there a way to achieve this?
Probably you have some condition state depending on which you present your "modal-like" view, so depending on the same condition you can disable below TabView, like below
TabView {
// ... tabs content here
}.disabled(showingModal)
Update: Here is a demo of approach that I meant (tested with Xcode 11.3+)
struct TestTabViewModal: View {
#State private var selectedTab = 0
#State private var modalShown = false
var body: some View {
ZStack {
TabView(selection: $selectedTab) {
VStack {
Button("Show Modal") { self.modalShown = true }
.padding(.top, 40)
Spacer()
}
.tabItem {
Image(systemName: "1.circle")
}.tag(0)
Text("2").tabItem {
Image(systemName: "1.circle")
}.tag(1)
}.disabled(modalShown)
if modalShown {
RoundedRectangle(cornerRadius: 10)
.fill(Color.yellow)
.frame(width: 320, height: 240)
.overlay(Button("CloseMe") { self.modalShown = false })
}
}
}
}

Resources