SwiftUI's NavigationView Is Missing The Back Button On Apple Watch - swiftui

I created an Apple Watch app using SwiftIU. The main view of the app is a List, with NavigationLink to another List. When I'm in one of the internal Lists, the back button isn't on the top of the view.
But when I swipe from the side of the screen (like on iOS), I go back to the main view.
Is there a way to add back the back button to the top of the screen? Is the new gesture the new "back button" for watchOS 7?
Xcode 12.2 (beta 3).
Apple Watch S6 (simulator, 7.1) and Apple Watch S5 (real, 7.0).
SwiftIU 2.0
The main view:
NavigationView {
List {
ForEach(listsModel.lists) { (list) in
NavigationLink(destination: ListView(list: list)
.environmentObject(list.rowsModel)) {
ListsRowView(list: list)
}
}
}
}
.onAppear {
listsModel.update()
}
.navigationTitle("ListsView.NavigationTitle")
The internal view:
List(rowsModel.rows) { (row) in
RowView(row: row, shouldUpdate: $shouldUpdate)
}
.navigationBarHidden(false)
.onAppear {
instantiateTimer()
rowsModel.update()
}
.onDisappear {
cancelTimer()
}
.onReceive(timer) { _ in
if shouldUpdate {
rowsModel.update()
}
}
.navigationTitle(list.name)
Update:
In the Canvas simulator, there is a back button, but on the regular simulator, it's not.
Canvas:
Regular:

Remove the NavigationView, so your code would be:
List {
ForEach(listsModel.lists) { (list) in
NavigationLink(destination: ListView(list: list)
.environmentObject(list.rowsModel)) {
ListsRowView(list: list)
}
}
}
.onAppear {
listsModel.update()
}
.navigationTitle("ListsView.NavigationTitle")
The reason for this is that watchOS always has system NavigationView added by default

Related

SwiftUI - NavigationLink and NavigationStack is not working with a button for iOS 16

I am making an app where I need to navigate to the home page when the user clicks on the Login button and when the Login button is clicked, the navigation link code is not working and shows a warning as Result of 'NavigationLink<Label, Destination>' initializer is unused. FYI, please refer to the attached screenshot and the below code:
import SwiftUI
struct LoginView: View {
var nextButton: some View {
HStack {
Button("Next") {
NavigationLink {
HomeView(user: user)
} label: {
Text("Test")
}
}
.buttonStyle(PlainButtonStyle())
.font(.system(size: 24))
}
}
var body: some View {
NavigationStack {
nextButton
}
}
}
There are two problems here:
A NavigationLink is an alternative to a Button and can be used on its own for navigation. You can use a button to navigate programatically, but for this simple case a link works. Like Buttons, you can change the appearance of NavigationLinks if needed.
NavigationStack is a more powerful replacement for NavigationView. You should not mix them both. As the HomeView is your root view, the single NavigationStack for your app should be there (not in the LoginView).
struct HomeView: View {
var body: some View {
NavigationStack {
VStack {
NavigationLink {
LoginView()
} label: {
Text("Login")
}
}
}
}
}
The warning is because you are creating a NavigationLink in the button action code block, and returned NavigationLink is not assigned or used.

SwiftUI navigationBarTitle not resetting to .large returning from Toolbar set to .inline

There are a few posts regarding SwiftUI .inline not resetting to .largeTitle when navigation returns to the parent:
For example:
Navigation bar title stays inline in iOS 15
and
Navigationbar title is inline on pushed view, but was set to large
While earlier posts seem to suggest this has been corrected, I'm running into the same problem, even in iOS 16, but I'm not using a < Back button, instead I'm using "Cancel" (and not show, "Save") on my DestinationView. My goal is to mimic Apple's practice of showing a modal view when adding data, but a show-style push on the navigation stack when viewing and editing existing data (e.g. Contacts app, Reminders app, Calendar app). The brief code below illustrates the problem without adding extra code to handle data updating (e.g. #EnviornmentObject).
When I run this in the Live Preview in Xcode 14.0.1, scheme set to iPhone 13 Pro, no problems. Click a NavLink, return from destination, and ContentView shows .large navigationBarTitle. BUT when I run in the simulator or on a 13 Pro device, returning to Home from a NavigationLink remains .inline unless I pull down on the list. If I switch to iPhone 14 Pro, the live preview looks fine, but the simulator shows a short of abrupt switch from inline back to large, not a smooth animation. Am I doing something wrong in the setup here or is there a bug in the implementation, noting that the behavior oddly holds to .inline on return home to ContentView, if I use this in either a simulator or device for iPhone 13 Pro. Thanks for guidance & insight!
struct ContentView: View {
#State private var sheetIsPresented = false
var items = ["Item1", "Item2", "Item3"]
var body: some View {
NavigationStack {
List {
ForEach(items, id: \.self) { item in
NavigationLink(item, destination: DestinationView(item: item))
.padding()
}
}
.navigationBarTitle("Home", displayMode: .large)
.listStyle(.plain)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
sheetIsPresented.toggle()
} label: {
Image(systemName: "plus")
}
}
}
}
.sheet(isPresented: $sheetIsPresented) {
NavigationStack {
DestinationView(item: "New!")
}
}
}
}
struct DestinationView: View {
var item: String
#Environment(.dismiss) private var dismiss
var body: some View {
List {
Text(item)
}
.toolbar {
ToolbarItem (placement: .navigationBarLeading) {
Button("Cancel") {
dismiss()
}
}
}
.listStyle(.plain)
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden()
}
}

SwiftUI Swipe Actions with Text on WatchOS don't work

I have a list to which I need to attach a swipe action:
struct TestListView: View {
var items = ["A", "B", "C"]
var body: some View {
List() {
ForEach(0 ..< items.count, id: \.self) { idx in
let item = items[idx]
Text("\(item)")
}
.swipeActions {
Button("Person") {
// Do Something
}
.tint(.red)
}
}
}
}
Unfortunately, the text doesn't appear on watchOS:
Exactly the same code works on iOS:
What am I doing wrong?
WatchOS seems to only allow images on these button, probably due to screen size. Try using Label instead of Text, with both an icon and title, and you should automatically get the correct behavior on both iOS and watchOS.

How do I get a ToolbarItem with primaryAction placement to display under the navigation bar on WatchOS in SwiftUI

I am trying to figure out how to hide the primaryAction ToolbarItem under the navigation bar in a watchOS app written in SwiftUI. Apple's documentation states
In watchOS the system places the primary action beneath the navigation bar; the user reveals the action by scrolling.
ToolbaritemPlacement - primaryAction
Here is the view that I am using:
var body: some View {
NavigationView {
ScrollView {
VStack(alignment: .leading) {
ForEach(0..<100) {
Text("Row \($0)")
}
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Settings") {}
}
}
}
}
.navigationTitle("Navigation")
}
The primary action button always displays and is never hidden. I have seen an example where one used a ScrollViewReader to programmatically change the position but I feel like that isn't what Apple has stated is possible and I'm trying to understand what I'm doing wrong. Apple's documentation also states that the toolbar needs to be inside the scrollview:
Place a toolbar button only in a scrolling view. People frequently scroll to the top of a scrolling view, so discovering a toolbar button is almost automatic. Placing a toolbar button in a nonscrolling view makes it permanently visible, eliminating the advantage of hiding it when it’s not needed.
Toolbar Buttons watchOS
Try this:
struct ContentView: View {
var body: some View {
ScrollView {
VStack(alignment: .leading) {
ForEach(0..<100) {
Text("Row \($0)")
}
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Settings") {}
}
}
.navigationTitle {
Text("Navigation")
}
}
}
}

How to set button focus in SwiftUI?

!!! TVOS !!!
I have list of buttons and it somehow autofocus first button in that list (when view is loaded). Is there any way how to focus another button in that list using SwiftUI?
I know there is preferredFocusedView in UIKit but id like to do this in SwiftUI.
Thanks for any advice!
SwiftUI 2.0
Note: !! Partial solution !!
The following approach, as tested with Xcode 12b5 / tvOS 14 works only with stacks and does not work (in any tested combination) for List/ScrollView.
Anyway for small on-screen sets of buttons it is applicable so worth posting.
struct DemoPreferredFocusView: View {
#Namespace var ns
#Environment(\.resetFocus) var resetFocus
#AppStorage("initialButton") var initialButton: Int = 3
var body: some View {
VStack {
ForEach(0..<10, id: \.self) { i in
Button(action: {
print(">> tapped \(i)")
self.initialButton = i
}) {
Text("Button \(i)")
}
.prefersDefaultFocus(i == initialButton, in: ns)
}.focusScope(ns)
}
.onAppear {
DispatchQueue.main.async {
self.resetFocus.callAsFunction(in: ns)
}
}
}
}