Post not yet marked as solved
I've implemented a navigation stack that appends the next view's view model so that the appending and .navigationDestination looks like the below. Whenever I press the continue button to add the viewModel to the path in the ContentView (the child of BumperScreen and the second in the path), the app just stops. I tried isolating the problem with a test project using the same methodology for navigation and have been able to navigate several views, almost infinitely. I cannot replicate this issue and am having a hard time understanding why one is working, and not the other.
struct BumperScreen: View {
@State private var path = NavigationPath()
@StateObject var viewModel: BumperScreenViewModel
@StateObject var sheetManager = SheetManager()
init(viewModel: @autoclosure @escaping () -> BumperScreenViewModel) {
self._viewModel = .init(wrappedValue: viewModel())
}
var body: some View {
if isDoneOnboarding {
HomeView()
.environmentObject(sheetManager)
} else {
NavigationStack(path: $path) {
ZStack {
Color(.Gold)
.ignoresSafeArea()
VStack {
if show {
Spacer()
loadAnimation
.frame(width: 150, height: 150)
.task {
try? await viewModel.getDataFromAPI()
try? await Task.sleep(for: Duration.seconds(1))
path.append(ContentViewViewModel())
doneLoading.toggle()
show.toggle()
}
Spacer()
} else if doneLoading {
EmptyView()
} else {
launchAnimation
}
}
.navigationDestination(for: ContentViewViewModel.self) { model in
ContentView(viewModel: model, path: $path)
.environmentObject(sheetManager)
}
}
}
}
}
}
Which leads the user to the second view
struct ContentView: View {
@EnvironmentObject var sheetManager: SheetManager
@StateObject var viewModel: ContentViewViewModel
@Binding var path: NavigationPath
var body: some View {
ZStack {
Color(.trulliGold)
.ignoresSafeArea()
VStack {
Spacer()
headerStatement
Spacer()
VStack {
privacyPolicy
bluetoothPolicy
locationsPolicy
notificationsPolicy
apprunningPolicy
}
Spacer()
Spacer()
continueButton
}
}
.popup(with: sheetManager)
.navigationBarBackButtonHidden()
}
var continueButton: some View {
Button(action: {
print("pressed")
path.append(WelcomeScreenViewViewModel())
}) {
Text("CONTINUE")
}
.navigationDestination(for: WelcomeScreenViewViewModel.self) { model in
WelcomeScreenView(viewModel: model, path: $path)
}
}
}
Which goes to
struct WelcomeScreenView: View {
@StateObject var viewModel: WelcomeScreenViewViewModel
@Binding var path: NavigationPath
var body: some View {
ZStack {
Color(.trulliGold)
.ignoresSafeArea()
VStack(alignment: .center, spacing: 8) {
header
Spacer()
tabView
Spacer()
Spacer()
nextButton
closeButton
}
}
.navigationDestination(for: SetupGuideSpeakerSearchViewViewModel.self) { model in
SetupGuideSpeakerSearchView(viewModel: model, path: $path )
}
.navigationBarBackButtonHidden()
}
}
I've tried isolating the issue to a debug project and have been able to navigate several views therein with no issue. debug project
Post not yet marked as solved
In https://developer.apple.com/wwdc22/10054 it is shown that the best way to organize an iPad layout is by:
struct TwoColumnContentView: View {
@Binding var showExperiencePicker: Bool
@EnvironmentObject private var navigationModel: NavigationModel
var categories = Category.allCases
var dataModel = DataModel.shared
var body: some View {
NavigationSplitView(
columnVisibility: $navigationModel.columnVisibility
) {
List(
categories,
selection: $navigationModel.selectedCategory
) { category in
NavigationLink(category.localizedName, value: category)
}
.navigationTitle("Categories")
.toolbar {
ExperienceButton(isActive: $showExperiencePicker)
}
} detail: {
NavigationStack(path: $navigationModel.recipePath) {
RecipeGrid(category: navigationModel.selectedCategory)
}
}
}
}
NavigationModel defined as
final class NavigationModel: ObservableObject, Codable {
@Published var selectedCategory: Category?
@Published var recipePath: [Recipe]
@Published var columnVisibility: NavigationSplitViewVisibility
}
But what happens when in a child view, it triggers off some action in some other ViewModel that needs to update the navigation stack?
struct ChildViewModel {
func childViewCalledAndDidSomething {
//now I need to update recipePath, how?
}
}
Post not yet marked as solved
How do we test SwiftUI views?
Can we test them in Unit testing? If so we don't have proper apis to get the view hierarchy or the required set of objects for testing. We have to rely on using identifier for every object and extract the view with the id?
I have seen many using ViewInspector which extracts the view hierarchy and has apis to test the array of views matching a text etc. But is it using a right approach for extracting views will it have issues when ever the Xcode or Os is upgraded?
Is snapshot testing right approach for swiftUI views?
Post not yet marked as solved
On my Watch app pre-WatchOS 9 I had code as follows:
`
TabView {
NavigationView {
TodayView(model: delegate.model)
.navigationBarTitleDisplayMode(.large)
}
NavigationView {
SettingsView(model: delegate.model)
}
}
.tabViewStyle(.page)
However, for WatchOS 9 I'm using the new NavigationStack like this:
`
NavigationStack {
TabView {
TodayView(model: delegate.model)
.navigationBarTitleDisplayMode(.large)
The issue is, with WatchOS 9 the title always displays as .inline and never large. I've tried embedding a list or scroll view. I've also tried placing a navigationView within the NavigationStack and TabView, that just makes duplicate titles.
So, using the NavigationStack with a TabView on WatchOS 9, how do you get large titles?
Thanks
I'm trying to create a UI layout and navigation functionality similar to the Apple Fitness app on Apple Watch.
This is all WatchOS 9 so no need for older API support, phew!
I want to have NavigationTitle set for each view in a TabBar.
I want each selected NavigationDestination to hide the TabBar controls and disable swiping when opened.
I want the NavigationTitle and update to remain whilst navigating between Tabs and Childs.
I've created this sample code based off the Building a productivity app for Apple Watch sample code from Apple and other examples on navigation with the new NavigationStack in WatchOS 9, iOS 16 etc...
import SwiftUI
@main
struct Test_Watch_App_Watch_AppApp: App {
@SceneBuilder var body: some Scene {
WindowGroup {
TabView {
NavigationStack {
ScrollView {
VStack {
NavigationLink("Mint", value: Color.mint)
NavigationLink("Pink", value: Color.pink)
NavigationLink("Teal", value: Color.teal)
}
}
.navigationDestination(for: Color.self) { color in
Text("Hello").background(color)
}
.navigationTitle("Colors")
}
NavigationStack {
ScrollView {
VStack {
NavigationLink("headline", value: Font.headline)
NavigationLink("title", value: Font.title)
NavigationLink("caption", value: Font.caption)
}
}
.navigationDestination(for: Font.self) { font in
Text("Hello").font(font)
}
.navigationTitle("Fonts")
}
}.tabViewStyle(.page)
}
}
}
The problem here is, when selecting any of the Navigation Links, the child view still displays the Tab Bar page indicators and allows you to swipe between the tabs, I don't want this. The functionality as I'd like exists int eh Apple Fitness app on the Watch. The app launches in the Activity Tab. You can swipe across to Sharing, select a person, and in that view it's not then possible to swipe straight back to Activity.
I've tried Embedding the whole TabView in a NavigationStack and removing the NavigationStacks per tab. This works as far as fixing the child views hiding the TabBar page indicator controls and swiping. However, it then breaks NavigationTitles on launch and when moving in and out of Childs, so I don't think it should be this way.
Any help very appreciated.
Post not yet marked as solved
Hi All! I am working on a Multiplatform SwiftUI app and I've encountered an issue with NavigationSplitView. My app's ContentView is a NavigationSplitView sidebar and detail. The sidebar is a list with several NavigationLinks. (I haven't adopted programmatic navigation because of the simplicity this sidebar.) One of the NavigationLinks brings the user to a Table with several rows and columns. Then, when I go to select an item on the table, a row shows selected for a split second, before deselecting itself again. Then, I can use the table as normal. This happens every time the view is opened.
The reason I think that this is an issue with NavigationSplitView is because when I use the legacy NavigationView, everything is fine. Is this normal behavior for NavigationSplitView, and if it is, how to I make it so that my table receives focus when the NavigationLink is open?
NavigationSplitView does not run .task for each detail view instance. I'm trying to update my code from NavigationView to NavigationSplitView. I was having trouble, since I rely upon .task to update the @State of my detail view. It does not update this state in the detail view when using NavigationSplitView. Please see my comments in the code snippet below. Does anyone have any suggestions? FWIW I have also filed FB11932889.
Thanks for any help.
import SwiftUI
enum Category: CaseIterable, Hashable {
case first
case second
}
struct CategoryRow: View {
let category: Category
var body: some View {
Text("Row: \(String(describing: category))")
}
}
struct CategoryDetail: View {
enum LoadingState {
case none
case loading
case loaded
case error
}
let category: Category
@State private var loadingState: LoadingState = .none
var body: some View {
VStack {
Text("Category: \(String(describing: category))")
Text("LoadingState: \(String(describing: loadingState))")
}
.task {
// If this .task does not run ever, the view will show a .none Category.
// If this .task runs, the view will start by showing .loading, and then .loaded
// The bug found when this is in CategoryNavigationSplitView that: this .task does not run for the .second category,
// but the @State is not reset, it will show .loaded for the second category chosen!
// I expect this .task to run each time, and not carry over the @State from
// previous CategoryDetail. It must be a new CategoryDetail View, since the
// let Category constant is changing.
// When this is in CategoryNavigationView, the .task runs as expected.
loadingState = .loading
do {
try await Task.sleep(for: .seconds(2))
loadingState = .loaded
} catch {
loadingState = .error
}
}
}
}
struct CategoryNavigationSplitView: View {
@State var selectedCategory: Category?
var body: some View {
NavigationSplitView {
List(Category.allCases, id: \.self, selection: $selectedCategory) { category in
NavigationLink(value: category) { CategoryRow(category: category) }
}
} detail: {
if let selectedCategory {
CategoryDetail(category: selectedCategory)
} else {
Text("Select a Category")
}
}
}
}
struct CategoryNavigationView: View {
@State var selectedCategory: Category?
var body: some View {
NavigationView {
List(Category.allCases, id: \.self, selection: $selectedCategory) { category in
NavigationLink {
CategoryDetail(category: category)
} label: {
CategoryRow(category: category)
}
}
}
Text("Select a Category")
}
}
Post not yet marked as solved
I need help with this topic please.
I'm interested in the three column layout version.
Column one I want my list of Core Data Entities,
Column two I want a list of my Core Data Entity's ListView items, and for
Column three I want the detail view of the selected list view item.
Any help on figuring out the best way to accomplish this would be much appreciated.
Thanks
Post not yet marked as solved
Any one getting any issues with NavigaitonLink to seemingly innocuous views freezing when tapped on? 1 CPU at 100% memory steadily increasing until app gets killed by the system. Will freeze if any NavigationLink on the view is tapped if certain views are linked to using NavigaitonLink.
I note some people have been getting similar freezes if they use @AppStorage, but I'm not using @AppStorage. I do use CoreData tho. tho I have some views that use core data that don't freeze.
https://developer.apple.com/forums/thread/708592?page=1#736374022
has anyone experienced similar issues? or know the cause. it doesn't seem to be any of my code because if I pause the debugger it stops on system code.
I'm trying to build an app that has a NavigationSplitView and a NavigationStack in the detail View.
The normal flow works fine, but if I navigate to the second page on the detail view and then select another menu item (i.e. the second item), I'm still on the detail page of the first menu item.
The underlying view of the second detail view changes. This can be observed by the change of the back button label.
How do I ensure that my NavigationStack is also reset when I change the selection?
import SwiftUI
enum Option: String, Equatable, Identifiable {
case first
case second
var id: Option { self }
}
struct ContentView: View {
@State private var selection: Option?
var body: some View {
NavigationSplitView {
List(selection: $selection) {
NavigationLink(value: Option.first) {
Text("First")
}
NavigationLink(value: Option.second) {
Text("Second")
}
}
} detail: {
switch selection {
case .none:
Text("Please select one option")
case .some(let wrapped):
NavigationStack {
DetailView(title: wrapped.rawValue)
}
}
}
.navigationSplitViewStyle(.balanced)
}
}
struct DetailView: View {
private var title: String
init(title: String) {
self.title = title
}
var body: some View {
List {
NavigationLink {
Text(title)
} label: {
Text("Show \(title) detail")
}
}
.navigationTitle(title)
}
}
Post not yet marked as solved
Hi, I've recently experienced a weird crash that only happening on iOS 16 device. When dismiss a sheet which contains NavigationView it sometime crashes. It is not 100% reproducible but relatively easy to get crash. The same build running on iOS 15 device is totally fine. The crash report shows nothing useful and the stack trace shows none of my code so I'm not sure what's going on in here. Any help is much appreciated.
2022-09-28_14-52-44.0187_+1300-aa6ef929623289970774d4bf7cc3f8835df8fbce.crash
Post not yet marked as solved
Below is my deprecated solution to immediately go the the destination view when creating a new item with the "plus"-Button. I don't get how to migrate the deprecated 'init(destination:tag:selection:label:)'
to the new NavigationStack or NavigationSplitView
Any help appreciated.
struct ItemList: View {
@StateObject private var itemStore = ItemStore()
@State private var indexItem: Int?
var body: some View {
NavigationView {
List {
let itemStoreZip = zip(itemStore.allItems.indices,
itemStore.allItems)
ForEach(Array(itemStoreZip), id: \.0) {i, item in
let itemBind = $itemStore.allItems[i]
NavigationLink(destination: ItemDetail(item: itemBind),
tag: i,
selection: $indexItem) {
ItemRow(item: item)
}
}
.onDelete { indexSet in
itemStore.removeItem(indexSet: indexSet)
}
.onMove { indices, newOffset in
itemStore.moveItem(source: indices, to: newOffset)
}
}
.navigationTitle("Homepwner")
.toolbar {
ToolbarItem(placement: ToolbarItemPlacement.navigationBarTrailing) {
EditButton()
}
ToolbarItem(placement: ToolbarItemPlacement.navigationBarLeading) {
Button {
(indexItem,_) = itemStore.newItem()
} label: {
Image(systemName: "plus")
}
}
}
}
}
}
Post not yet marked as solved
Is it possible to drive NavigationSplitView navigation with a view in sidebar (left column) that is not a List? All examples that I have seen from this year only contain List in sidebar.
I ask this because I would like to have a more complex layout in sidebar (or first view on iOS) that contains a mix of elements, some of them non-interactive and not targeting navigation. Here’s what I would like to do:
import SwiftUI
struct Thing: Identifiable, Hashable {
let id: UUID
let name: String
}
struct ContentView: View {
let things: [Thing]
@State private var selectedThingId: UUID?
var body: some View {
NavigationSplitView {
ScrollView(.vertical) {
VStack {
ForEach(things) { thing in
Button("Thing: \(thing.name) \( selectedThingId == thing.id ? "selected" : "" )") {
selectedThingId = thing.id
}
}
SomeOtherViewHere()
Button("Navigate to something else") { selectedThingId = someSpecificId }
}
}
} detail: {
// ZStack is workaround for known SDK bug
ZStack {
if let selectedThingId {
Text("There is a thing ID: \(selectedThingId)")
} else {
Text("There is no thing.")
}
}
}
}
}
This actually works as expected on iPadOS and macOS, but not iOS (iPhone). Tapping changes the selection as I see in the button label, but does not push anything to navigation stack, I remain stuck at home screen.
Also filed as FB10332749.
Post not yet marked as solved
I am in the process of migrating from NavigationView as a root to NavigationSplitView. My model supports various different objects, and what would fit into the content column varies from a list, to a TabView with a list within. This particular example (which I refer to from here on,) the lists hold two different objects within the model. The layout looked like this:
NavigationView // Used for sidebar, would now be NavigationSplitView(sidebar:content:detail)
- NavigationLink -> TabView
-> List(objectA)
-> Object-Specific DetailView
-> List(objectB)
-> Object-Specific DetailView
- NavigationLink -> List(objectC)
-> Object-Specific DetailView
- and more like the above
As it stood, there was no "third column" functionality, but with the new API I am seeking to achieve this.
I am however, hung up on how to change the detail pane properly from within these sub-views. Am I needing to pass the various objects up to the struct housing the root NavigationSplitView?
I don't currently use anything like Scene Storage, or other frameworks currently besides using CoreData with CloudKit. I know there's lots I need to be reading, is there somewhere I can go that really goes through UI Navigation and Elements?
The end goal is a three-column application (including the sidebar as the first) that dynamically changes the content column from the definition of NavigationSplitView, but with a clear solution to changing the detail column to display different views that are specific to each object.
Nesting a NavigationStack would come close if it could adjust the parent NavigationSplitView's detail column?
I'm playing around with the new navigation API's offered in ipadOS16/macOS13, but having some trouble working out how to combine NavigationSplitView, NavigationStack and NavigationLink together on macOS 13 (Testing on a Macbook Pro M1). The same code does work properly on ipadOS.
I'm using a two-column NavigationSplitView. Within the 'detail' section I have a list of SampleModel1 instances wrapped in a NavigationStack. On the List I've applied navigationDestination's for both SampleModel1 and SampleModel2 instances.
When I select a SampleModel1 instance from the list, I navigate to a detailed view that itself contains a list of SampleModel2 instances. My intention is to navigate further into the NavigationStack when clicking on one of the SampleModel2 instances but unfortunately this doesn't seem to work. The SampleModel2 instances are selectable but no navigation is happening.
When I remove the NavigationSplitView completely, and only use the NavigationStack the problem does not arise, and i can successfully navigate to the SampleModel2 instances.
Here's my sample code:
// Sample model definitions used to trigger navigation with navigationDestination API.
struct SampleModel1: Hashable, Identifiable {
let id = UUID()
static let samples = [SampleModel1(), SampleModel1(), SampleModel1()]
}
struct SampleModel2: Hashable, Identifiable {
let id = UUID()
static let samples = [SampleModel2(), SampleModel2(), SampleModel2()]
}
// The initial view loaded by the app. This will initialize the NavigationSplitView
struct ContentView: View {
enum NavItem {
case first
}
var body: some View {
NavigationSplitView {
NavigationLink(value: NavItem.first) {
Label("First", systemImage: "house")
}
} detail: {
SampleListView()
}
}
}
// A list of SampleModel1 instances wrapped in a NavigationStack with multiple navigationDestinations
struct SampleListView: View {
@State var path = NavigationPath()
@State var selection: SampleModel1.ID? = nil
var body: some View {
NavigationStack(path: $path) {
List(SampleModel1.samples, selection: $selection) { model in
NavigationLink("\(model.id)", value: model)
}
.navigationDestination(for: SampleModel1.self) { model in
SampleDetailView(model: model)
}
.navigationDestination(for: SampleModel2.self) { model in
Text("Model 2 ID \(model.id)")
}
}
}
}
// A detailed view of a single SampleModel1 instance. This includes a list
// of SampleModel2 instances that we would like to be able to navigate to
struct SampleDetailView: View {
var model: SampleModel1
var body: some View {
Text("Model 1 ID \(model.id)")
List (SampleModel2.samples) { model2 in
NavigationLink("\(model2.id)", value: model2)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
(NOTE: also created this issue on stackoverflow here)
Also created a feedback assistant item: FB10876902
Post not yet marked as solved
Hi,
From what I saw, the NavigationStack allows for different types but replaces the view on top of the previous view.
I want to use the sidebar in the NavigationSplitView in a similar way to the macOS "Systems Settings" app, basically more like a top-level menu leading to different views instead of the same content view.
The only option I see is sticking with destination-based NavigationLinks which breaks all the advantages of switching to the new way.
Does navigationDestination(for:destination:) work for NavigationSplitView? It would be so intuitive, especially with the NavigationSplitView defaulting to a Stack's behavior on smaller screens.
If not, how can such a menu or multi-type list be achieved?
Post not yet marked as solved
I got the error:
A NavigationLink is presenting a value of type “FormTemplate” but there is no matching navigationDestination declaration visible from the location of the link. The link cannot be activated.
This problem is only occurring on iPhone, on iPad works fine.
I was following the documentation, and can't find the error.
That's my base tabbar view:
TabView {
ForEach(menuData, id: \.title) { item in
NavigationStack {
item.view
}
.tabItem {
Text(item.title)
Image(systemName: item.icon)
}
.tag(item.title)
}
}
The menu view:
ScrollView {
VStack (spacing: 12) {
ForEach(lstMenu, id: \.title) { tV in
NavigationLink(destination: tv.view, tag: tv.title, selection: $selection) {
rowView
}
}
}
.navigationTitle("Segurança")
.padding(.top, 20)
.padding(.trailing, 20)
.padding(.leading, 20)
}
.navigationTitle("Segurança")
The list view:
List {
ForEach($viewModel. forms), id: \.documentId) { form in
NavigationLink(value: form) {
NameContentRow(entity: form)
.frame(height: 75)
.contentShape(Rectangle())
}
}
}
.navigationDestination(for: FormTemplate.self) { form in
EventFormResponseList(form: form)
.vlPermissions(permissions)
}
.refreshable {
fetch(onPullToRefresh: true)
}
.listStyle(.insetGrouped)
.navigationBarTitleDisplayMode(.inline)
.navigationBarTitle("Formulários de eventos")
.padding(.horizontal, sizeClass == .compact ? 0 : 20)
.padding(.top, sizeClass == .compact ? 0 : 16)
.task {
viewModel.loadFormTemplate(completion: { _ in })
}
Post not yet marked as solved
Hi there,
I'm struggling with the new NavigationSplitView.
struct Team: Identifiable, Hashable {
let id = UUID()
var name: String
var players: [String]
}
struct SplitView3: View {
@State private var teams = [
Team(name: "Packers", players: ["Rodgers", "Alexander", "Dillon"]),
Team(name: "Vikings", players: ["Ingram", "Cousins", "Berry"])
]
@State private var selectedTeam: Team?
@State private var selectedPlayer: String?
var body: some View {
NavigationSplitView {
List(teams, selection: $selectedTeam) { team in
Text(team.name).tag(team).navigationTitle("Team")
}.navigationBarHidden(true)
.navigationSplitViewColumnWidth(250)
} content: {
List(selectedTeam?.players ?? [], id: \.self, selection: $selectedPlayer) { player in
Text(player).navigationTitle("Team Member")
}
} detail: {
Form{
Text(selectedPlayer ?? "Choose a player.")
}
.navigationTitle("Player Detail").navigationBarTitleDisplayMode(.inline)
}
.navigationSplitViewStyle(.automatic)
}
}
When I run this in Simulator or Canvas I can navigate from Team (sidebar), trough Teammembers (content) to detail view and back to sidebar.
But after I reach the sidebar again the links forward to content view don't work anymore.
Did I miss something?
Post not yet marked as solved
I’ve been trying really hard to get to try NavigationSplitView in my app, but I’m really confused as to how to use it when I’m not passing values in my app. I just have bindings. I really want to use the new features that come with the new API, but I just can’t figure out how. Attached is what I’m doing now, any help figuring out how to do this exact thing with the new NavigationSplitView API would be greatly appreciated.
// ContentView
var body: some View {
NavigationView {
Sidebar() .navigationTitle("Formats")
Detail()
}
}
// Sidebar
@State private var isShowingDetail = true
var body: some View {
List {
NavigationLink(destination: Detail().navigationTitle(“Detail"), isActive: $isShowingMLAView) {
Label(“Detail", systemImage: “ellipsis.circle")}
// Other Navigation Links here...
Post not yet marked as solved
I noticed a problem with the NavigationCookbook sample code. The builtInRecipes array containing Recipe models without IDs which means state restoration fails because the recipes have new unique IDs every time the app is launched (there is a let id = UUID() in Recipe). The problem is in NavigationCookbook/Models/DataModel
private let builtInRecipes: [Recipe] = {
var recipes = [
"Apple Pie": Recipe(
name: "Apple Pie", category: .dessert,
ingredients: applePie.ingredients),
Should be
let builtInRecipes: [Recipe] = {
var recipes = [
"Apple Pie": Recipe(
id: UUID(uuidString: "E35A5C9C-F1EA-4B3D-9980-E2240B363AC8")!,
name: "Apple Pie", category: .dessert,
ingredients: Ingredient.fromLines(applePie)),
And the same thing for all the other built-in recipes in the array. The builtInRecipes containing ids can be found in the Code tab in the Developer app for this samples WWDC session video:
https://developer.apple.com/wwdc22/10054
I also submitted this as feedback FB11744612