Post

Replies

Boosts

Views

Activity

iOS18 SwiftUI Bug: Theme Change Issue
Yet another bug, I suppose, on iOS 18. This time it relates to the SwiftUI framework, and to be precise, it's about the theme change functionality. It no longer works properly on iOS 18, while it’s not an issue on iOS 17. Demo app available at: https://github.com/m4rqs/iOS18ThemeIssue Steps to reproduce: Assume the system is set to use the light theme. Go to the Settings tab. Switch the colour scheme to Dark. Switch the colour scheme to System (this no longer works). Switch the colour scheme to Light (the tab bar is not updated if the content page is long enough and go beyond current view). Switch between tabs (tab bar icons are greyed) enum Theme: Int { case system case light case dark } struct SettingsView: View { @State private var theme: Theme = .system @State private var colorScheme: ColorScheme? = nil var body: some View { Form { Section(header: Text("General")) { Picker("Colour Scheme", selection: $theme) { Text("System").tag(Theme.system) Text("Light").tag(Theme.light) Text("Dark").tag(Theme.dark) } .preferredColorScheme(colorScheme) .onChange(of: theme) { switch theme { case .light: colorScheme = .light case .dark: colorScheme = .dark default: colorScheme = nil } } } } } }
3
4
492
Sep ’24
iOS 18 SwiftData Bug: Codable Models Cause Relationship Mapping Error
Here we have yet another bug, I suppose, in SwiftData that happens on iOS18 but it is not an issue on iOS17. There are 2 models defined as follows @Model final public class Note: Identifiable, Codable, Hashable { public private(set) var uuid = UUID().uuidString var heading: String = "" var tags: [Tag]? init(heading: String = "") { self.heading = heading } required public init(from decoder: Decoder) throws { ... } public func encode(to encoder: Encoder) throws { ... } } @Model final public class Tag: Identifiable, Codable { var name: String = "" @Relationship(deleteRule: .nullify, inverse: \Note.tags) var notes: [Note]? init(_ name: String) { self.name = name } required public init(from decoder: Decoder) throws { … } public func encode(to encoder: Encoder) throws { ... } } and a function o add new tags as follows private func addTags(note: Note, tagNames: [String]) { if note.tags == nil { note.tags = [] } for tagName in tagNames { if let tag = fetchTag(tagName) { if !note.tags!.contains(where: {$0.name == tagName}) { note.tags!.append(tag) } } else { // The following line throws the exception on iOS18 when Tag conforms to Codable: // Illegal attempt to map a relationship containing temporary objects to its identifiers. note.tags!.append(Tag(tagName)) } } } This code works perfectly well on iOS17 but on iOS18 I get the exception “Illegal attempt to map a relationship containing temporary objects to its identifiers.” What I noticed that this happens only when Tag model conforms to Codable protocol. Is it a bug? It looks like, otherwise we've got some undocumented changes have been made. In my previous post I mentioned about the other issue about ModelContext that is broken too on iOS18 - I mean it works perfectly well on iOS17. Demo app with an example how to workaround this problem is available here on GitHub. Repro steps: Add a note with some tags (separated by space) Edit this note and add a new tag (tag that does not exists in database) and tap Save. You should noticed that the tag hasn't been added. It works occasionally but hardly to be seen.
3
5
483
Sep ’24
SwiftData does not persist change on relationship on iOS 18 beta 7
I came across of something I'm struggling to comprehend. I've got an iOS app based on SwiftUI and SwiftData + CloudKit. I wrote it using Xcode 15 and the target was iOS 17. Everything works fine in this environment, but after upgrading my phone to iOS 18 beta 7 something very strange started to happen with SwiftData on a physical device and in the simulator. Every time when data is updated, to be precise - when the relationship is modified, the change is reverted after 15 seconds! I've got the following settings on and nothing can be seen it's going on there in the logs -com.apple.CoreData.Logging.stderr 1 -com.apple.CoreData.CloudKitDebug 1 -com.apple.CoreData.SQLDebug 1 -com.apple.CoreData.ConcurrencyDebug 1 Here you are some simplified code extraction: @Model final public class Note: Identifiable, Hashable { public private(set) var uuid = UUID().uuidString var notification: Notification? ... } @Model final public class Notification: Identifiable, Hashable { var dateId: String = "" @Relationship(deleteRule: .nullify, inverse: \Note.notification) var notes: [Note]? init(_ dateId: String) { self.dateId = dateId } } @ModelActor final public actor DataModelActor : DataModel { public func updateNotification(oldDate: Date, newDate: Date? = nil, persistentModelId: PersistentIdentifier) { if let note = modelContext.model(for: persistentModelId) as? Note { updateNotification(oldDate: oldDate, newDate: newDate, note: note) } try? self.modelContext.save() } private func updateNotification(oldDate: Date? = nil, newDate: Date? = nil, note: Note) { if let oldDate = oldDate { let notifications = fetchNotifications() let oldDateId = NotificationDateFactory.getId(from: oldDate) // removing the note from the collection related to oldDate if let notification = notifications.first(where: { $0.dateId == oldDateId }) { if let notificationNotes = notification.notes { if let notificationNoteIndex = notification.notes!.firstIndex(of: note) { notification.notes!.remove(at: notificationNoteIndex) } if notification.notes == nil || notification.notes!.isEmpty { self.modelContext.delete(notification) } } } } if let newDate = newDate, newDate > Calendar.current.startOfToday() { // adding to a new collection related to newDate let notifications = fetchNotifications() let newDateId = NotificationDateFactory.getId(from: newDate) if let notification = notifications.first(where: { $0.dateId == newDateId }) { note.notification = notification } else { let notification = Notification(newDateId) note.notification = notification } } } } Spreading save method here and there does not help :( I've used Core Data Lab software to look into database and I can clearly see data changes are reverted for relationship property. Example: In Notification database there is one element: 2024-08-26 (3) with 3 notes attached. I modified one note to send notification on 2024-08-27. Changes in database reflects situation correctly showing: 2014-08-26 (2) 2024-08-27 (1) BUT!!! After 15 seconds doing noting database looks like this: 2024-08-26 (3) 2024-08-27 (0) All changes were reverted and all notes are still attached to the same date as they were at the first place. Any thoughts?
3
1
768
Aug ’24
Navigation issue in Xcode 16 beta 7
I've got an iOS app and a main view with a list with a search field. When I click on a list item navigation link takes me to another view through navigation destination modifier. I can go back to the main list view. Now, consider this scenario: Click in the search field (you do not have to type there anything) Click any item on the list This all works fine when I build the app with Xcode 15.4 and I can get back to the main list. But if I build the app using Xcode 16 beta 7 the destination view is shown twice (the destination view is shown and then moved again to the same view) and I cannot back to the main view at all. I can go back one view only. Could it be a bug in Xcode? Was do you think about that?
3
0
495
Aug ’24
Implement UNUserNotificationCenterDelegate in iOS app using Swift6
I've got a problem with compatibility with Swift6 in iOS app that I have no idea how to sort it out. That is an extract from my main app file @MainActor @main struct LangpadApp: App { ... @State private var notificationDataProvider = NotificationDataProvider() @UIApplicationDelegateAdaptor(NotificationServiceDelegate.self) var notificationServiceDelegate var body: some Scene { WindowGroup { TabView(selection: $tabSelection) { ... } .onChange(of: notificationDataProvider.dateId) { oldValue, newValue in if !notificationDataProvider.dateId.isEmpty { tabSelection = 4 } } } } init() { notificationServiceDelegate.notificationDataProvider = notificationDataProvider } } and the following code shows other classes @MainActor final class NotificationServiceDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { var notificationDataProvider: NotificationDataProvider? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { UNUserNotificationCenter.current().delegate = self return true } func setDateId(dateId: String) { if let notificationDataProvider = notificationDataProvider { notificationDataProvider.dateId = dateId } } nonisolated func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async { // After user pressed notification let content = response.notification.request.content if let dateId = content.userInfo["dateId"] as? String { await MainActor.run { setDateId(dateId: dateId) } } } nonisolated func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions { // Before notification is to be shown return [.sound, .badge, .banner, .list] } } @Observable final public class NotificationDataProvider : Sendable { public var dateId = "" } I have set Strict Concurrency Checking to 'Complete.' The issue I'm facing is related to the delegate class method, which is invoked after the user presses the notification. Current state causes crash after pressing notification. If I remove "nonisolated" keyword it works fine but I get the following warning Non-sendable type 'UNNotificationResponse' in parameter of the protocol requirement satisfied by main actor-isolated instance method 'userNotificationCenter(_:didReceive:)' cannot cross actor boundary; this is an error in the Swift 6 language mode I have no idea how to make it Swift6 compatible. Does anyone have any clues?
0
11
528
Aug ’24
How to handle predicate with optionals in SwiftData
Xcode: 15.1 I've got (simplified) model with relationship many-to-many defined as follows @Model final class Item { var title: String @Relationship(inverse: \Tag.items) var tags: [Tag]? init(_ title: String) { self.title = title } } @Model final class Tag { var name: String var items: [Item]? init(_ name: String) { self.name = name } } and a view with a query struct ItemsView: View { @Query var items: [Item] var body: some View { List {...} } init(searchText: String) { _items = Query(filter: #Predicate<Item> { item in if (searchText.isEmpty) { return true } else { return item.tags!.contains{$0.name.localizedStandardContains(searchText)} } }) } } This code compiles but fails at runtime with an error: Query encountered an error: SwiftData.SwiftDataError(_error: SwiftData.SwiftDataError._Error.unsupportedPredicate) It looks like Predicate does not like optionals cause after changing tags and items to non optionals and the predicate line to item.tags.contains{$0.name.localizedStandardContains(searchText)} everything works perfectly fine. So, my question is, does anybody know how to make it work with optionals? Full code: https://github.com/m4rqs/PredicateWithOptionals.git
4
1
1.6k
Dec ’23
Sheet doesn't adapt current system colour scheme
Let's assume that default colour system scheme is Light mode. If I open this app and open settings window then change colour scheme to Dark and then change again to System, the main window get back the Light mode but the settings window remains in Dark mode. If I change default colour system scheme is Dark mode, then open this app, open settings window then change colour scheme to Light and then change again to System, the main window get back the Dark mode but the settings window remains in Light mode. What am I doing wrong? Here is simplified code to demo my issue import SwiftUI struct ContentView: View { @State private var isSettingsViewPresented = false @State private var colorScheme = ColorScheme?.none var body: some View { NavigationStack { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) Text("Hello, world!") } .toolbar { ToolbarItem { Button { isSettingsViewPresented.toggle() } label: { Label("Settings", systemImage: "gear") } } } .sheet(isPresented: $isSettingsViewPresented) { NavigationStack { Form { Picker("Colour Scheme", selection: $colorScheme) { Text("System").tag(ColorScheme?.none) Text("Light").tag(Optional(ColorScheme.light)) Text("Dark").tag(Optional(ColorScheme.dark)) } } .toolbar { ToolbarItem(placement: .confirmationAction) { Button("Done") { isSettingsViewPresented.toggle() } } } } .preferredColorScheme(colorScheme) } } .preferredColorScheme(colorScheme) } }
0
0
269
Nov ’23