This is a question regarding the specification of ObservalObject.
The following code does not cause a compilation error when the deployment target is set to iOS 16, but it does cause the following error when set to iOS 15:
class Model: ObservableObject {
let player: AVPlayer
@Published var isPlaying = false
var playerObserver: NSKeyValueObservation?
init() {
self.player = AVPlayer()
self.playerObserver = self.player.observe(\.rate, options: [.new, .old]) { player, change in
NSLog("changed rate: \(String(describing: change.newValue))")
}
}
func setup() {
let name = "sample"
let url = Bundle.main.url(forResource: name, withExtension: "m4a")!
let playerItem = AVPlayerItem(url: url)
self.player.replaceCurrentItem(with: playerItem)
}
func play() {
self.player.play()
self.isPlaying = true
}
}
The following error occurs in iOS 15:
Cannot form key path to main actor-isolated property 'rate'
Call to main actor-isolated instance method 'play()' in a synchronous nonisolated context
Additionally, if the code is modified as follows, the error does not occur even in iOS 15. Specifically, adding @MainActor to the Model resolves the issue.
@MainActor
class Model: ObservableObject {
...
}
From this behavior, I guessed that ObservableObject is implicitly a MainActor in iOS 16 and later. Is this understanding correct?
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Created
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)
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
Hello,
I have a simple example using StateObject and List. When I bind the List(selection:) to a property of the StateObject like this:
List(selection: $viewModel.selectedIndex) { ... }
I noticed that each time I push the view using a NavigationLink, a new instance of the StateObject is created. However, when I pop the view, the deinit of the StateObject is not called.
When is deinit actually expected to be called in this case?
Example code:
import SwiftUI
@main
struct NavigationViewDeinitSampleApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
}
}
}
}
struct Item: Hashable {
let text: String
}
@MainActor
fileprivate class ContentViewModel: ObservableObject {
@Published var selectedIndex: Int? = nil
init() {
NSLog("ContentViewModel.init")
}
deinit {
NSLog("ContentViewModel.deinit")
}
}
struct ContentView: View {
@StateObject private var model = ContentViewModel()
let items: [Item] = {
return (0...10).map { i in
Item(text: "\(i)")
}
}()
var body: some View {
List(selection: $model.selectedIndex) {
ForEach(items.indices, id: \.self) { idx in
let item = items[idx]
NavigationLink {
ContentView()
} label: {
Text(item.text)
}
}
}
}
}
Interestingly, if I instead use a plain @State variable inside the View:
@State private var selectedIndex: Int?
...
List(selection: $selectedIndex) { ... }
Then the deinit of the StateObject does get called when the view is popped.
Because there's no sign of deinit being triggered in the first pattern, I’m starting to suspect this might be a SwiftUI bug. Has anyone seen this behavior or have more information about it?
Thanks in advance.
Environment:
Xcode: 16.4(16F6)
iOS Simulator: iPhone SE3 iOS16.4(20E247),iPhone SE3 iOS 18.4(22E238)
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)