SwiftUI NavigationView show master on left - swiftui

I have some master detail in Swift UI. Here it is:
import SwiftUI
struct MenuItemView: View {
var title: String
#Environment(\.presentationMode) var presentationMode
private var btnBack : some View { Button(action: {
//if landscape just show list on the left
//same if swipe from left
//how to do that???
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Menu")
}
}
var body: some View {
Text(title)
.navigationBarTitle(Text(title), displayMode: .inline)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: btnBack)
}
}
struct ContentView: View {
#State private var items: [String] = ["Home", "Events", "Topics", "Latest"]
var body: some View {
NavigationView {
List {
ForEach(self.items, id: \.self) { item in
NavigationLink(destination: MenuItemView(title: item)) {
Text(item)
}
}
}
.listStyle(GroupedListStyle())
.navigationBarTitle(Text("TimelessToday"), displayMode: .inline)
Text("Initial")
}
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
This is working exactly as I plan in portrait mode for any IPhone. But in landscape for IPhone 11 or for IPad in portrait I want "Menu" button when pressed to show left menu with list of items. Same like when doing swipe from left side to show it.
Can't understand how to do this.

Related

SwiftUI .searchable implementation in the wrong way?

I am trying to use a tab bar in order to use different views. On some of those views I have a list of items and I wish that list to be .searchable. If I go to each of the views and search it works like a charm, but when I embed that in the tabbed view the list becomes non-responsive to click but it responds to scroll gesture.
I will expand the idea with code that I have and screenshots, but I am pretty sure that the problem resides in how I'm implementing the combination of the tab bar view and the views that have the searchable modifier:
This code works well
import SwiftUI
struct ClientListView: View {
#ObservedObject var viewModel = ClientFeedViewModel()
#State var searchText: String
#State private var showingSheet = false
#State private var showList = false
var clients: [Client] {
if searchText.count > 2 {
return searchText.isEmpty ? viewModel.clients : viewModel.search(withText: searchText)
}
return viewModel.clients
}
init(){
searchText = ""
}
var body: some View {
NavigationView {
List(clients) { client in
NavigationLink(destination: {
}, label: {
VStack {
Text(client.clientName)
}
})
.listRowSeparator(.hidden)
}
.searchable(text: $searchText)
.listStyle(.plain)
}
}
}
struct ClientListView_Previews: PreviewProvider {
static var previews: some View {
ClientListView()
}
}
The problem starts when I do this and implement the ClientListView in a tab bar view like this:
Tab bar with different views not working searchable modifier
This is the code of the Tab Bar View:
import SwiftUI
struct MainTabView: View {
#EnvironmentObject var viewModel: AuthViewModel
#Binding var selectedIndex: Int
var body: some View {
NavigationView {
VStack {
TabView(selection: $selectedIndex) {
ClientListView()
.onTapGesture {
selectedIndex = 0
}
.tabItem {
Label("Clients", systemImage: "list.bullet")
}.tag(0)
ProjectListView()
.onTapGesture {
selectedIndex = 1
}
.tabItem {
Image(systemName: "person")
Label("Projects", systemImage: "list.dash")
}.tag(1)
TaskListView()
.tabItem {
Image(systemName: "person")
Label("Tasks", systemImage: "list.dash")
}.tag(2)
.onTapGesture {
selectedIndex = 2
}
ClientListView()
.tabItem {
Label("Settings", systemImage: "gear")
}.tag(3)
.onTapGesture {
selectedIndex = 3
}
}
.navigationTitle(tabTitle)
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Image("logo_silueta")
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
viewModel.signOut()
}, label: {
Text("logout")
})
}
}
.navigationBarTitleDisplayMode(.inline)
}
}
var tabTitle: String {
switch selectedIndex {
case 0: return "Clients"
case 1: return "Projects"
case 2: return "Tasks"
case 3: return "Settings"
default: return ""
}
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView(selectedIndex: .constant(0))
}
}
Navigation on the tabbed view works and displays the different names on the tab bar title, but when I click cancel or x button of the search bar, it doesn't work and also the list becomes unclickable
So far I haven't been able to find where the problem is but I am assuming its because the tab bar view is messing up with the searchable property
The culprit would seem to be your .onTapGesture modifiers, which will take precedence over any tap handling in your child views.
I'm not sure what value those modifiers bring, since using appropriate .tag values is enough for the tab view to keep track of its selected index. I'd start by removing them.
#ObservedObject var viewModel = ClientFeedViewModel() is a memory leak, try changing it to something like:
struct ClientListViewData {
var searchText: String = ""
var showingSheet = false
var showList = false
mutating func showSheet() {
showingSheet = true
}
}
struct ClientListView: View {
#Binding var data: ClientListViewData

SwiftUI Sidebar Doesn't Remember Screen

I'm having a problem where when I close the app to send it to the background and then reopen it, it doesn't go to the last screen. Instead it seems to reset just like if the app were being opened from scratch.
I've included an example below. Run it on an iPad in landscape mode, select "Favorites". The details controller will show a red screen. Close the app to send it to the background, open any other app, then go back to the test app. You'll see that it reset itself to the green view. It should stay on the red view.
I've taken all of my code straight from the Fruta example project which doesn't have this behavior so I have no idea what's going on.
EDIT
I've made SideBar a standalone list like Asperi suggested, and I'm also now using SceneStorage as Apple recommends. Using SceneStorage solves the sidebar issue at first, but the core problem is still there when I'm multiple levels deep in a navigation stack.
In this updated example if you tap on Numbers in the sidebar, then select a row, leave the app, and come back after doing something else, the sidebar selection resets.
I have discovered that this is only a problem if your app supports multiple windows. If you uncheck that box none of this seems to be necessary.
The example below has the most recent code edits.
EDIT
I reached out to Apple Code Level Support and they re-debited my account telling me that my issue might be a bug and advised me to file a radar. I’m not entirely sure that it’s a framework bug though.
struct ContentView: View {
#if os(iOS)
#Environment(\.horizontalSizeClass) private var horizontalSizeClass
#endif
var body: some View {
#if os(iOS)
if horizontalSizeClass == .compact {
AppTabNavigation()
} else {
AppSidebarNavigation()
}
#else
AppSidebarNavigation()
#endif
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// MARK: - AppTabNavigation
struct AppTabNavigation: View {
#State private var selection: Tab = .menu
enum Tab {
case menu
case favorites
case rewards
case recipes
}
var body: some View {
TabView(selection: $selection) {
NavigationView {
Color.purple
}
.tabItem {
Label("Menu", systemImage: "list.bullet")
.accessibility(label: Text("Menu"))
}
.tag(Tab.menu)
NavigationView {
Color.orange
}
.tabItem {
Label("Favorites", systemImage: "heart.fill")
.accessibility(label: Text("Favorites"))
}
.tag(Tab.favorites)
NavigationView {
Color.red
}
.tabItem {
Label("Numbers", systemImage: "book.closed.fill")
.accessibility(label: Text("Numbers"))
}
.tag(Tab.recipes)
}
}
}
enum NavigationItem: String {
case menu
case favorites
case recipes
}
struct SideBar: View{
#Binding var selection: String?
var body: some View {
List(selection: $selection) {
NavigationLink(destination: Color.green, tag: NavigationItem.menu.rawValue, selection: $selection) {
Label("Menu", systemImage: "list.bullet")
}
NavigationLink(destination: Color.red, tag: NavigationItem.favorites.rawValue, selection: $selection) {
Label("Favorites", systemImage: "heart")
}
NavigationLink(destination: ListView(), tag: NavigationItem.recipes.rawValue, selection: $selection) {
Label("Numbers", systemImage: "book.closed")
}
}
.listStyle(SidebarListStyle())
}
}
struct AppSidebarNavigation: View {
#SceneStorage("ContentView.selection") private var selection: String?
var body: some View {
NavigationView {
SideBar(selection: $selection)
Text("Select a category")
.foregroundColor(.secondary)
}
}
}
struct ListView: View{
#SceneStorage("ListView.selection") private var selection: String?
var body: some View{
List(0..<20){ num in
NavigationLink(destination: ListDetails(num: num), tag: num.description, selection: $selection) {
Text(num.description)
}
}
}
}
struct ListDetails: View{
let num: Int
var body: some View{
Text(num.description)
.font(.title)
}
}
Computable property sidebar reevaluated causing List rebuilding, use instead standalone view, like
enum NavigationItem {
case menu
case favorites
case recipes
}
struct AppSidebarNavigation: View {
#State private var selection: NavigationItem? = .menu
#State private var presentingRewards = false
var body: some View {
NavigationView {
SideBarView(selection: $selection)
Text("Select a category")
.foregroundColor(.secondary)
}
}
}
struct SideBarView: View {
#Binding var selection: NavigationItem?
var body: some View {
List(selection: $selection) {
NavigationLink(destination: Color.green, tag: NavigationItem.menu, selection: $selection) {
Label("Menu", systemImage: "list.bullet")
}
.tag(NavigationItem.menu)
NavigationLink(destination: Color.red, tag: NavigationItem.favorites, selection: $selection) {
Label("Favorites", systemImage: "heart")
}
.tag(NavigationItem.favorites)
NavigationLink(destination: Color.blue, tag: NavigationItem.recipes, selection: $selection) {
Label("Recipes", systemImage: "book.closed")
}
.tag(NavigationItem.recipes)
}
.listStyle(SidebarListStyle())
}
}

Why is SwiftUI presented view displayed incorrectly?

I've created a SwiftUI "multiplatform" (iOS and macOS) app from the Xcode 12 beta 6 (12A8189n) app template.
My issue is that one of my views, AnotherView, is displaying incorrectly. Here's a gif showing the problem. Notice that AnotherView displays with the navigation stack already pushed to a non-existent view. Tapping the back button reveals the expected screen, however it is displayed only partially filling the expected area.
Here's the code:
TestNavigationApp.swift
import SwiftUI
#main
struct TestNavigationApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#State private var presentingFirstView = false
var body: some View {
Button(action: { self.presentingFirstView = true }) {
Text("Present First View")
}
.sheet(isPresented: $presentingFirstView) {
FirstView(isPresented: $presentingFirstView)
}
}
}
FirstView.swift
import SwiftUI
struct FirstView: View {
#Binding var isPresented: Bool
var body: some View {
NavigationView {
EmbeddedView()
.navigationBarTitle("First View", displayMode: .large)
}
}
}
EmbeddedView.swift
import SwiftUI
struct EmbeddedView: View {
#State private var presentingAnotherView = false
var body: some View {
VStack {
Text("Embedded View")
Button(action: { self.presentingAnotherView = true }) {
Text("Present Another View")
}
}
.sheet(isPresented: $presentingAnotherView) {
AnotherView(isPresented: $presentingAnotherView)
}
}
}
AnotherView.swift
import SwiftUI
struct AnotherView: View {
#Binding var isPresented: Bool
var body: some View {
NavigationView {
Text("Another View")
.navigationBarTitle("Another View", displayMode: .large)
}
}
}
Anyway, not really sure what's happening here. Any suggestions appreciated.
Try to use navigation view style explicitly
var body: some View {
NavigationView {
Text("Another View")
.navigationBarTitle("Another View", displayMode: .large)
}.navigationViewStyle(StackNavigationViewStyle())
}

SwiftUI - Using Navigation View and Sheets correctly

Having issues with a NavigationView and Sheet. I have the below flow:
- ContentView: Has button that opens ContentView2 sheet
- ContentView2: Has NavigationLink with header that goes to ContentView3
- ContentView3: Has NavigationLink, no header, that directs users to ContentView2
However, when I set up the above flow, I end up getting stacked headers when users go back and forth between ContentView2 and ContentView3. How would I prevent this and only have 1 header when users go back and forth between the two views? Thanks!
struct ContentView: View {
#State var showSheet = false
var body: some View {
Button("Click"){
self.showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
ContentView2()
}
}
}
struct ContentView2: View {
var body: some View {
NavigationView {
NavigationLink(destination: ContentView3()){
Text("Click Here")
}
.navigationBarTitle("Bar Title", displayMode: .inline)
}
}
}
struct ContentView3: View {
var body: some View {
NavigationLink(destination: ContentView2()){
Text("Click Here")
}
}
}
You need only one NavigationView in root, so here is corrected components
struct ContentView: View {
#State var showSheet = false
var body: some View {
Button("Click"){
self.showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
NavigationView { // only here !!
ContentView2()
}
}
}
}
struct ContentView2: View {
var body: some View {
NavigationLink(destination: ContentView3()){
Text("Click Here")
}
.navigationBarTitle("Bar Title", displayMode: .inline)
}
}

SwiftUI dismiss modal sheet presented from NavigationView (Xcode Beta 5)

I am attempting to dismiss a modal view presented via a .sheet in SwiftUI - called by a Button which is within a NavigationViews navigationBarItems, as per below:
struct ModalView : View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button(action: {
self.presentationMode.value.dismiss()
}, label: { Text("Save")})
}
}
struct ContentView : View {
#State var showModal: Bool = false
var body: some View {
NavigationView {
Text("test")
.navigationBarTitle(Text("Navigation Title Text"))
.navigationBarItems(trailing:
Button(action: {
self.showModal = true
}, label: { Text("Add") })
.sheet(isPresented: $showModal, content: { ModalView() })
)
}
}
}
The modal does not dismiss when the Save button is tapped, it just remains on screen. The only way to get rid of it is swiping down on the modal.
Printing the value of self.presentationMode.value always shows false so it seems to think that it hasn't been presented.
This only happens when it is presented from the NavigationView. Take that out and it works fine.
Am I missing something here, or is this a beta issue?
You need to move the .sheet outside the Button.
NavigationView {
Text("test")
.navigationBarTitle(Text("Navigation Title Text"))
.navigationBarItems(trailing:
Button("Add") {
self.showModal = true
}
)
.sheet(isPresented: $showModal, content: { ModalView() })
}
You can even move it outside the NavigationView closure.
NavigationView {
Text("test")
.navigationBarTitle(Text("Navigation Title Text"))
.navigationBarItems(trailing:
Button("Add") { self.showModal = true }
)
}
.sheet(isPresented: $showModal, content: { ModalView() })
Notice you can also simplify the Button call if you have a simple text button.
The solution is not readily apparent in the documentation and most tutorials opt for simple solutions. But I really wanted a button in the NavigationBar of the sheet that would dismiss the sheet. Here is the solution in six steps:
Set the DetailView to not show.
Add a button to set the DetailView to show.
Call the .sheet(isPresented modifier to display the sheet.
Wrap the view that will appear in the sheet in a NavigationView because we want to display a .navigationBarItem button.
PresentationMode is required to dismiss the sheet view.
Add a button to the NavBar and call the dismiss method.
import SwiftUI
struct ContentView: View {
// 1
#State private var showingDetail = false
var body: some View {
VStack {
Text("Hello, world!")
.padding()
Button("Show Detail") {
showingDetail = true // 2
}
// 3
.sheet(isPresented: $showingDetail) {
// 4
NavigationView {
DetailView()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct DetailView: View {
// 5
#Environment(\.presentationMode) var presentationMode
var body: some View {
Text("Detail View!")
// 6
.navigationBarItems(leading: Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "x.circle")
.font(.headline)
.foregroundColor(.accentColor)
})
}
}