This is a report of an issue that appears to be a regression regarding NavigationStack.
I have been aware of an issue where views are being automatically popped within NavigationView / NavigationStack since iOS 15, and it seems to be reoccurring in iOS 18.0 beta2.
Below is the reproducible code. Additionally, in my environment, this problem does not occur iOS 18 simulator, but it does happen on an iPhone XS Max(real device) with iOS 18 beta 2.
Environment:
Xcode: Version 16.0 beta (16A5171c)
iOS: 18.0 (22A5297f)
iPhone: XS Max (real device)
import SwiftUI
@main
struct iOS16_4NavigationSample2App: App {
var body: some Scene {
WindowGroup {
NavigationStack {
NavigationLink {
ContentView()
} label: {
Text("Content")
}
}
}
}
}
enum Kind { case none, a, b, c }
struct Value: Hashable, Identifiable {
let id: UUID = UUID()
var num: Int
}
@MainActor
class ContentModel: ObservableObject {
@Published var kind: Kind = .a
@Published var vals: [Value] = {
return (1...5).map { Value(num: $0) }
}()
init() {}
}
struct ContentView: View {
@StateObject private var model = ContentModel()
@State private var selectedData: Value?
@State private var isShowingSubView = false
@Environment(\.dismiss) private var dismiss
init() {
}
var body: some View {
if #available(iOS 16.0, *) {
List(selection: $selectedData) {
ForEach(model.vals) { val in
NavigationLink(value: val) {
Text("\(val.num)")
}
}
}
.navigationDestination(isPresented: .init(get: {
selectedData != nil
}, set: { newValue in
if !newValue && selectedData != nil {
selectedData = nil
}
}), destination: {
SubView(kind: model.kind)
})
}
}
}
struct SubView: View {
init(kind: Kind) {
print("init(kind:)")
}
init() {
print("init")
}
var body: some View {
Text("Content")
}
}
This code was shared in a different issue [iOS 16.4 NavigationStack Behavior Unstable].
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I've tested the behavior of NavigationStack in iOS 16.4 and found that it doesn't work in my already published app. Of course, my app works perfectly fine with the NavigationStack in iOS 16 to iOS 16.3.
I'm not sure about the cause, but when tapping on a NavigationLink in a very large and complex navigation hierarchy, the corresponding navigationDestination doesn't transition the screen and instead gets caught in an infinite loop. Has anyone else experienced similar issues?
I'd like to prepare a sample program to reproduce the issue, but it doesn't occur in simple view hierarchies, so I haven't been able to prepare one yet.
Although the release notes for iOS 16.4 mention changes related to the performance of the NavigationStack, I suspect that some instability has been introduced.
This behavior reminds me of the unintended automatic pop issue in the NavigationView of iOS 15.
In the iOS 16.4 environment, I am planning to revert to using NaviagtionView.
Once I have prepared a sample program to reproduce the issue, I will update again.
There are bugs in NaviagtionView as well, but in the iOS 16.4 environment, I am temporarily planning to revert to using it.
Once I have prepared a sample program to reproduce the issue, I will update again.
I've encountered a critical issue while testing my app, which is available on the App Store, on the iOS 17.2 beta (iPhone SE simulator). The app freezes and becomes unresponsive. Currently, I haven't found a workaround, which means my app is completely non-functional and untestable on iOS 17.2 beta.
The app supports iOS 15.2 and later versions, and it has been working fine from iOS 15.2 through iOS 17.1. The problem only occurs on the iOS 17.2 beta.
I have been able to reproduce the issue with the sample code provided below.
■ Test Environment:
macOS: 14.0 (23A344)
Xcode Version: 15.1 beta (15C5042i)
iPhone SE 3rd generation (simulator): iOS 17.2 (21C5029e)
■ Steps to Reproduce:
Prerequisites: Prepare an audio file, such as an m4a or mp3, and add it to the Bundle Resources.
1 Launch the sample code provided below.
2 Tap on any row's NavigationLink.
After tapping, when the program attempts to access the AVPlayer, the following log is displayed in Xcode:
CA_UISoundClient.cpp:1127 Device = 0
HALPlugInManagement.cpp:396 HALPlugInManagement::RegisterPlugIns: loading in-process plug-ins
AddInstanceForFactory: No factory registered for id <CFUUID 0x600000285600> F8BB1C28-BAE8-11D6-9C31-00039315CD46
CA_UISoundClient.cpp:1203 renderer = 0x600000011f90, synchronizer: 0x0, repeat: 0, vol: 1.00
3 Tap the Navigation's Back button at the top left.
Depending on the timing, there may be no response when pressing the button (screen). Approximately 15 seconds later, the following log is displayed, and the screen becomes responsive again as it returns to the previous view.
AQMEIO.cpp:198 timed out after 15.000s (0 0); suspension count=0 (IOSuspensions: )
MEDeviceStreamClient.cpp:467 AQME Default-InputOutput: client stopping after failed start: <CA_UISoundClientBase@0x104015d00>; running count now 0
CA_UISoundClient.cpp:293 CA_UISoundClientBase::StartPlaying: AddRunningClient failed (status = -66681).
changing items while animating can result in a corrupted navigation bar
4 If the issue does not reproduce, quit the app and then return to step 1.
In the sample code, whether the issue occurs or not may depend on the timing of screen transitions, scrolling, and tapping. (The issue might occur with a high probability if you tap 'back' during a screen transition animation.) On the production app in question, the issue occurs 100% of the time.
■ Sample code
import SwiftUI
import AVFoundation
@main
struct iOSAppHangSampleApp: App {
@StateObject var model = ContentModel()
var body: some Scene {
WindowGroup {
if #available(iOS 17, *) {
NavigationStack {
ContentView()
.environmentObject(model)
}
} else {
NavigationView {
ContentViewIOS15()
.environmentObject(model)
}.navigationViewStyle(.stack)
}
}
}
}
@MainActor
class ContentModel: ObservableObject {
private let player = AVPlayer()
@Published var playbackDuration: TimeInterval = .zero
func load() {
let url = Bundle.main.url(forResource: "YourAudioFilename", withExtension: "m4a")! // Change to your audio.
let avassetURL = AVURLAsset(url: url, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
let avPlayerItem = AVPlayerItem(asset: avassetURL)
self.player.replaceCurrentItem(with: avPlayerItem)
self.playbackDuration = avPlayerItem.asset.duration.seconds
}
}
@available(iOS 17, *)
struct ContentView: View {
@EnvironmentObject private var model: ContentModel
private let urls: [URL] = {
(0..<50).map { URL(fileURLWithPath: "\($0)")}
}()
@State private var selected: Int?
var body: some View {
List(selection: $selected) {
ForEach(urls.indices, id: \.self) { idx in
let _ = urls[idx]
NavigationLink(value: idx) {
Text("\(idx)")
}
}
}
.navigationDestination(item: $selected) { idx in
Content3View()
.environmentObject(model)
}
}
}
@available(iOS 15, *)
struct ContentViewIOS15: View {
@EnvironmentObject var model: ContentModel
let urls: [URL] = {
(0..<50).map { URL(fileURLWithPath: "\($0)")}
}()
@State var selected: Int?
var body: some View {
List() {
ForEach(urls.indices, id: \.self) { idx in
let _ = urls[idx]
NavigationLink(tag: idx, selection: $selected) {
if selected == idx {
Content3View()
.environmentObject(model)
}
} label: {
Text("\(idx)")
}
}
}
}
}
struct Content3View: View {
@EnvironmentObject private var model: ContentModel
var body: some View {
Text("duration: \(model.playbackDuration)")
.onAppear() {
model.load()
}
}
}
■ Investigation
The sample code has been tested on the iPhone 17.0 simulator, 15.2 simulator, and an iPhone 17.1 real device, but the issue only appears on the 17.2 beta. Also, when replacing NavigationStack with NavigationView in the sample code, the issue does not seem to occur on iOS 17.2.
I'm not sure about the relationship between the back navigation and toolbar inside NavigationStack, but it might be related to the following content in the iOS 17.2 Release Notes.
Resolved Issues
* Fixed: Resolved a possible Swift access conflict crash that could occur with toolbar items. (113992797)
This issue also brings to mind the randomness associated with NavigationView / NavigationStack that I've observed.
iOS 16.4 NavigationStack Behavior Unstable https://developer.apple.com/forums/thread/727282
SwiftUI NavigationView pops back when updating observableObject https://developers.apple.com/forums/thread/693137
■ if anyone has found a workaround, please let me know.
I have not been able to confirm whether this issue occurs only on the Simulator, but it is preventing me from continuing to test on iOS 17.2 beta since the app is completely non-functional. If this is an issue with iOS 17.2 beta, I hope for a resolution before the official release of iOS 17.2.
This is a report of an issue that appears to be a regression regarding NavigationStack.
While investigating another issue [iOS18 beta2: NavigationStack, Views Being Popped Automatically] , I encountered this separate issue and wanted to share it.
In a NavigationStack with three levels: RootView - ContentView - SubView,
tapping the Back button from the SubView returned to the RootView instead of the ContentView.
This issue is similar to one that I previously posted regarding iOS16.0 beta.
https://developer.apple.com/forums/thread/715970
Additionally, there is no transition animation when moving from ContentView to SubView.
The reproduction code is as follows:
import SwiftUI
struct RootView2: View {
@State var kind: Kind = .a
@State var vals: [Selection] = {
return (1...5).map { Selection(num: $0) }
}()
@State var selection: Selection?
var body: some View {
if #available(iOS 16.0, *) {
NavigationStack {
NavigationLink {
ContentView2(vals: $vals, selection: $selection)
} label: {
Text("album")
}
.navigationDestination(isPresented: .init(get: {
return selection != nil
}, set: { newValue in
if !newValue {
selection = nil
}
}), destination: {
if let selection {
SubView2(kind: .a, selection: selection)
}
})
}
} else {
EmptyView()
}
}
}
struct ContentView2: View {
@Binding var vals: [Selection]
@Binding var selection: Selection?
@Environment(\.dismiss) private var dismiss
var body: some View {
list
.onChange(of: self.selection) { newValue in
print("changed: \(String(describing: newValue?.num))")
}
}
@ViewBuilder
private var list: some View {
if #available(iOS 16.0, *) {
List(selection: $selection) {
ForEach(self.vals) { val in
NavigationLink(value: val) {
Text("\(String(describing: val))")
}
}
}
}
}
}
//
struct SubView2: View {
let kind: Kind
let selection: Selection
var body: some View {
Text("Content. \(kind): \(selection)")
}
}
I've been testing my app on iOS 17 Public Beta and noticed a bug where the FocusState always returns nil depending on the combination of Views.
This issue did not occur on iOS 16 and earlier.
Below is the code where FocusState does not work.
As far as I've checked, the issue only occurs in the case below. Does anyone have a workaround?
Test environment:
Xcode Version 15.0 (15A240d)
iOS 17.0(21A329) (iPhone)
@main
struct SampleApp: App {
var body: some Scene {
WindowGroup {
ContentView() ///<-- 1
}
}
}
struct ContentView: View {
@State var isShowingCover = false
var body: some View {
Button("Show") {
isShowingCover = true
}
.fullScreenCover(isPresented: $isShowingCover) {
NavigationStack { ///<-- 2
SheetView()
}
}
}
}
struct SheetView: View {
@State private var texts: [String] = [
"file",
"folder",
]
@FocusState var focusing: Int?
var body: some View {
VStack {
Text("focusing: \(String(describing: focusing))")
List { ///<-- 3
TextFields(texts: $texts, focusing: $focusing)
}
Button(">") {
// FocusState always becomes nil in iOS 17
if let focusing { self.focusing = (focusing + 1) % texts.count }
else { self.focusing = 0 }
}
}
}
}
public struct TextFields: View {
@Binding var texts: [String]
var focusing: FocusState<Int?>.Binding
public var body: some View {
HStack {
ForEach(texts.indices, id: \.self) { idx in
TextField("", text: $texts[idx])
.focused(focusing, equals: idx)
.underline(idx == focusing.wrappedValue)
}
}
}
}
Interestingly, removing the NavigationStack within fullScreenCover makes FocusState work as expected. (2)
Also, if the ContentView in WindowGroup is changed to NavigationStack { SheetView() } (1) or the List (3) is removed, FocusState still works as expected.
/// 1
@main
struct MultilineFieldSampleApp: App {
var body: some Scene {
WindowGroup {
// ContentView()
NavigationStack {
SheetView() // FocusState works.
}
}
}
}
///2
struct ContentView: View {
@State var isShowingCover = false
var body: some View {
Button("Show") {
isShowingCover = true
}
.fullScreenCover(isPresented: $isShowingCover) {
// NavigationStack {
SheetView() // Also, FocusState works.
// }
}
}
}
/// 3
struct SheetView: View {
// ...
var body: some View {
VStack {
Text("focusing: \(String(describing: focusing))")
// List {
TextFields(texts: $texts, focusing: $focusing) // FocusState works.
// }
Button(">") {
if let focusing { self.focusing = (focusing + 1) % texts.count }
else { self.focusing = 0 }
}
}
}
}
Summary
On iOS 26, the navigation bar unexpectedly switches to a Light appearance during/after a view transition while the device/app is in Dark Mode. This seems correlated with applying listStyle(.plain) to a List. Removing .plain prevents the issue, but my app’s layout requires it.
Sample code:
import SwiftUI
@main
struct iOS26NavigationTitleSampleApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
.navigationTitle("Root")
.navigationBarTitleDisplayMode(.inline)
}
}
}
}
struct ContentView: View {
var body: some View {
VStack {
NavigationLink {
ListView()
} label: {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
}
}
.padding()
.toolbar {
ToolbarItemGroup(placement: .navigation) {
Button("Test") {
}
Button("Test2") {
}
}
}
}
}
struct ListView: View {
var items: [Int] = Array(0..<100)
var body: some View {
List {
ForEach(items.indices, id: \.self) { idx in
cell(items[idx])
}
}
.listStyle(.plain)
.toolbar {
ToolbarItemGroup(placement: .navigation) {
Button("Test") {
}
Button("Test2") {
}
}
}
.navigationTitle("TTT")
}
private func cell(_ item: Int) -> some View {
Text("\(item)")
}
}
Steps to Reproduce:
Set the device to Dark Mode.
Launch the sample app. → The root view’s navigation bar is in Dark appearance (as expected).
Tap “Hello World” to navigate. → On the destination view, the navigation bar becomes Light.
Navigate back to the root view. → The root view’s navigation bar now also remains Light.
Expected Result
The navigation bar should consistently retain the Dark appearance throughout navigation.
Notes
Removing listStyle(.plain) stops the issue (navigation bar stays Dark).
Simulator: Could not reproduce on iOS Simulator.
Devices: Reproducible on physical device.
Environment
Device: iPhone 15 Plus
OS: iOS 26 (23A341)
Xcode: 26.0 (17A324)
Hi, I've just finished replacing to NavigationStack, but I'm having trouble with this issue.
The following simple sample code works on iOS 16.0 production, but it doesn't work on iOS 16.1 beta.
When navigationDestination(isPresent:) is called, Xcode displays runtime warning message, but I cannot understand what the means.
A `navigationDestination(isPresented:content:)` is outside an explicit NavigationStack, but inside the detail column of a NavigationSplitView, so it attempts to target the next column. There is no next column after the detail column.
Did you mean to put the navigationDestination inside a NavigationStack?
I didn't use NavigationSplitView and navigationDestination(isPresent:) is inside NavigationStack.
Could someone please point out the problem with this code?
Or is it a SwiftUI bug?
Test environment
Xcode Version 14.1 beta 2 (14B5024i)
iPhone SE (3rd gen) (iOS 16.1)
Sample Code
@main
struct StackViewSampleApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
}
}
}
}
struct ContentView: View {
var body: some View {
List {
NavigationLink("SubView1") {
SubView1()
}
NavigationLink("SubView2") {
SubView2()
}
}
}
}
struct SubView1: View {
@State private var isPresent: Bool = false
var body: some View {
Button("A") {
isPresent = true
}
.navigationDestination(isPresented: $isPresent) {
Text("TEST")
}
}
}
struct SubView2: View {
enum ViewType {
case a, b, c
}
@State private var selection: ViewType? = nil
let viewTypes: [ViewType] = [.a, .b, .c]
var body: some View {
List(selection: $selection) {
ForEach(viewTypes, id: \.self) { t in
NavigationLink(value: t) {
Text("\(String(describing: t))")
}
}
}
.navigationDestination(isPresented: .init(get: {
selection != nil
}, set: { newValue in
if !newValue {
selection = nil
}
})) {
Text("\(String(describing: selection))")
}
}
}
The following sample code doesn't work as expected. In addition, the behavior differs between iOS 16.0 and iOS 16.1 beta.
If there is a workaround, please let me know.
(I know that navigationDestination(isPresent:destination:) can be substituted in iOS 16.0, but it doesn't work in iOS 16.1 beta)
I reported this issue (FB11599516).
@main
struct StackViewSampleApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
}
}
}
}
struct ContentView: View {
var body: some View {
VStack {
NavigationLink("SubView") {
SubView()
}
}
.navigationDestination(for: Int.self) { v in
DetailView(value: v)
}
}
}
struct SubView: View {
var body: some View {
NavigationLink(value: 1) {
Text("DetailView")
}
.navigationTitle("SubView")
}
}
struct DetailView: View {
let value: Int
var body: some View {
Text("\(value)")
.navigationTitle("Detail")
}
}
Actual behavior
iOS 16.0 behavior / Xcode Version 14.0 (14A309)
Pressing "SubView" button in RootView, it goes to SubView.
Pressing "DetailView" button in SubView, it goes to DetailView.
Pressing "Back" button, it goes to RootView. (Expect: it goes to SubView)
iOS 16.1 beta(20B5045d) / Xcode Version 14.1 beta 2 (14B5024i)
Pressing "SubView" button in RootView, it goes to SubView.
Pressing "DetailView" button in SubView, it goes to SubView.
Pressing "DetailView" button in SubView, it goes to SubView. (Repeat) (Expect: it goes to DetailView)
Pressing "Back" button in SubView, it goes to DetailView. (Expect: it goes to a previous view)
While testing my application on iOS 17 beta, I encountered an issue where the onAppear and onDisappear functions of the View inside the List went into an infinite loop, rendering the application inoperable. This did not occur with iOS 16 or iOS 15.
Is this a bug with the iOS 17 beta? Or did the specifications change with iOS 17?
Here is the reproduction code. On iOS 17 beta, as soon as the application starts, the onAppear and onDisappear of SampleSection go into an infinite loop.
Testing Environment:
Xcode: Version 14.3.1 (14E300c), Version 15.0 beta 3 (15A5195k)
Device: iPhone SE (2nd) iOS 15.2, iPhone SE (3rd) iOS 16.4, iPhone SE(3rd) iOS17 beta 3
import SwiftUI
@main
struct SampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
List {
SampleSection()
section // This issue occurs even without this line.
}
}
private var section: some View {
Section {
ForEach(1...100, id: \.self) { idx in
Text("\(idx)")
}
}
}
}
struct SampleSection: View {
@State private var isLoaded = false
var body: some View {
let _ = Self._printChanges()
Group {
if !isLoaded {
Section("Header") {}
.hidden()
} else {
Section("Header") {
Text("Text")
}
}
}
.onAppear {
NSLog("SampleSection onAppear.")
isLoaded = true
}
.onDisappear() {
NSLog("Sample Section onDisappear.")
isLoaded = false
}
}
}
In iOS 18, using .highPriorityGesture does not interfere with Button tap detection. However, on iOS 17 and earlier, setting a .highPriorityGesture causes the Button's tap action to stop responding.
I am using .highPriorityGesture in an attempt to support both tap and long-press gestures on a Button, which works as expected in iOS 18. However, the same implementation prevents taps on earlier versions.
Is using .highPriorityGesture on a Button not recommended in this case? Or is this an issue specific to how .highPriorityGesture behaves on iOS 17 and earlier?
Below is a sample code:
struct SampleButton: View {
let title: String
let action: () -> Void
var body: some View {
Button {
NSLog("Tapped")
action()
} label: {
Text(title)
}.highPriorityGesture(LongPressGesture(minimumDuration: 0.5).onEnded { _ in
NSLog("Long press.")
action()
})
}
}
struct ContentView: View {
var body: some View {
VStack {
SampleButton(title: "Tap or LongPress") {}
}
}
}
Environment:
Xcode: Version 16.3 (16E140)
iOS: iOS 18.4(simulator), iOS 17.5, iOS 16.4, iOS 15.2
This is a bug report. FB17433985
The layout of the following ContentView appears correctly when it is outside a NavigationStack. However, when the same view is placed inside a NavigationStack, the layout breaks.
It seems that the width of the List is being affected by the width of the buttonsView, which exceeds the screen width.
In my testing, this issue occurs on iOS 18.4 and later, but does not occur on iOS 18.2 or iOS 17.5.
Workaround I found:
Remove the fixed width modifier from the Button
If anyone knows of other ways to resolve this issue without affecting the layout, I would appreciate it if you could share them.
import SwiftUI
let values = (1...100).map { $0 }
let buttonTitles = (1...9).map { "Button\($0)" }
struct ContentView: View {
var body: some View {
VStack {
List {
Section {
ForEach(values.indices, id: \.self) { val in
HStack {
Text("Index: \(val)")
}
}
}
}
buttonsView
}
}
private var buttonsView: some View {
HStack {
ForEach(0..<buttonTitles.count, id: \.self) { index in
Button() {
} label: {
Image(systemName: "square.and.arrow.up")
.resizable()
.frame(width: 48, height: 48)
}
}
}
}
}
@main
struct ButtonShapeBugApp: App {
var body: some Scene {
WindowGroup {
if true {
NavigationStack {
ContentView()
}
} else {
ContentView()
}
}
}
}
Environment:
Xcode Version 16.3 (16E140)
iPhone 18.4.1 real device
iPhone SE3rd 18.4 simulator
Expect layout
Broken layout(9 buttons)
Broken layout(10 buttons)
My app calls Product.SubscriptionInfo.status(for:) to get the subscription status when the app starts.
Users with multiple Apple IDs have reported that every few days they get an unpurchased status, and when I checked that, the Product.SubscriptionInfo.status(for:) result array was empty.
(When the app is restarted, Product.SubscriptionInfo.status(for:) gives the correct result.)
StoreKit.Transaction.currentEntitlements, which is executed immediately after Product.SubscriptionInfo.status(for:), seems to be getting the correct result, so I am trying to check the subscription status with this result.
Is it a bug that Product.SubscriptionInfo.status(for:) returns an empty result for the purchaser?
There is a mismatch between Product.SubscriptionInfo.status(for:) and StoreKit.Transaction.currentEntitlements.
Is it possible for a mismatch to occur?
And In such a case, which result should be adopted?
The layout of buttons placed in the bottomBar of the iOS 17β SwiftUI toolbar is different from that in iOS 16 and earlier versions.
When I run the following code in Xcode 15 β3 (15A5195k) and the iOS 17β Simulator, all the buttons on the BottomBar are aligned to the left.
In environments including iOS 17β and earlier versions of iOS 16 with Xcode 14.3.1 (14E300c), only the last button is aligned to the right, while the other buttons are aligned to the left.
Is this a specification change in iOS 17, or is it a bug in iOS 17β?
import SwiftUI
@main
struct ToolBarSampleiOS17BApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
.navigationViewStyle(.stack)
}
}
}
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("iOS 17")
}
.toolbar {
ToolbarItemGroup(placement:.bottomBar) {
Button("A") { }
Button("B") { }
Button("C") { }
Button("D") { }
Button("E") { }
}
}
.padding()
}
}
Xcode 15 β3 (15A5195k) and the iOS 17β Simulator
earlier versions of iOS 16
Hi,
Recently, I have changed the price of an auto-renewable subscription. At that time, I made sure that existing subscribers maintain the previous price.
The issue is that product.displayPrice in StoreKit2 returns the new price even for those users who were able to maintain the old price.
Is there a way to retrieve the previous (actually applied to the user) price using StoreKit2?
Thank you.
When adding a keyboardShortcut to a Button that references the @StateObject model in Button.action as shown below, model.deinit is not called even if this view has disappeared. On redisplaying, both model.deinit and model.init are called.
Without the keyboardShortcut, model.deinit is called when the screen has disappeared.
Also, when I explicitly specified [weak model = self.model], the deinit was called.
Is it a bug that we need to use weak when using keyboardShortcut?
Test environment
Xcode Version 15.0 (15A240d)
iOS 17.0 Simulator iOS 16.4 Simulator, (iPhone SE 3rd generation)
import SwiftUI
@main
struct StateObjectDeinitSampleApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
MainView()
}
}
}
}
struct MainView: View {
var body: some View {
NavigationLink(value: true) {
Text("GO")
}
.navigationDestination(for: Bool.self) { value in
if value {
ContentView()
}
}
}
}
struct ContentView: View {
@StateObject var model = Model()
var body: some View {
VStack {
Button("Toggle") {
model.flag.toggle()
}
.keyboardShortcut(.space, modifiers: [])
}
.padding()
}
}
@MainActor
class Model: ObservableObject{
@Published var flag: Bool = false
init() {
print("Model.init")
}
deinit {
print("Model.deinit")
}
}
// weak.
struct ContentView: View {
@StateObject var model = Model()
var body: some View {
VStack {
Button("Toggle") { [weak model = self.model] in
model?.flag.toggle()
}
.keyboardShortcut(.space, modifiers: [])
}
.padding()
}
}