I’ve finally managed to get the migration working. What I did was basically:
Using FileManager to check whether the .sqlite file exists at the old application directory.
If that’s the case I can assume in my case that the migration wasn’t done, because after the migration this file will be deleted.
The migration itself loads the old NSPersistentContainer and migrates it to the app group with the coordinators replacePersistentStore(at: appGroupURL, withPersistentStoreFrom: legacyDataURL, type: .sqlite) function.
Remove all the old .sqlite files with the FileManagers .removeItem(at: legacyDataURL) function.
That whole migration check and actual location-migration is run before initializing the SwiftData ModelContainer, which then points to the app group url via ModelConfiguration(url: appGroupURL).
SwiftData will then automatically perform the SchemaMigrationPlan.
IMPORTANT: Please note that you need to keep the old .xcdatamodel file in your project, otherwise it will fail to create the Core Data container!
My Code
Following the Adopting SwiftData for a Core Data app example code from Apple, I’ve now moved my ModelContainer to an actor that shares the container via a singleton with the app and widget.
@main
struct MyAppName: App {
// App SwiftData Model Container
let sharedModelContainer = DataModel.shared.modelContainer
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
actor DataModel {
/// Singleton for entire app to use.
static let shared = DataModel()
/// Legacy location of the Core Data file.
private let legacyDataURL = URL.applicationSupportDirectory.appending(path: "MyApp.sqlite")
/// Location of the SwiftData file, saved in SQLite format.
private let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.domain.myapp")!.appendingPathComponent("MyApp.sqlite")
private init() {
// Checks for old Core Data migration before loading SwiftData.
checkCoreDataMigration()
}
nonisolated lazy var modelContainer: ModelContainer = {
let configuration = ModelConfiguration(url: appGroupURL)
do {
return try ModelContainer(
for: Foo.self, Bar.self,
migrationPlan: MigrationPlan.self,
configurations: configuration)
} catch {
fatalError("Could not create SwiftData ModelContainer: \(error)")
}
}()
nonisolated private func checkCoreDataMigration() {
// If old store does exist perform the migration to App Group!
// We can expect that the old store only exists if not yet migrated, because the migration deletes all old files.
if FileManager.default.fileExists(atPath: legacyDataURL.path(percentEncoded: false)) {
migrateCoreDataToAppGroup()
}
}
nonisolated private func migrateCoreDataToAppGroup() {
let container = NSPersistentContainer(name: "MyApp")
let coordinator = container.persistentStoreCoordinator
// 1. Migrate old Store
do {
// Replaces Application Support store with the one in the App Group.
try coordinator.replacePersistentStore(at: appGroupURL, withPersistentStoreFrom: legacyDataURL, type: .sqlite)
} catch {
print("Error replacing persistent store in App Group: \(error)")
}
// 2. Delete old Store files
NSFileCoordinator(filePresenter: nil).coordinate(writingItemAt: legacyDataURL, options: .forDeleting, error: nil) { url in
do {
try FileManager.default.removeItem(at: legacyDataURL)
try FileManager.default.removeItem(at: legacyDataURL.deletingLastPathComponent().appendingPathComponent("MyApp.sqlite-shm"))
try FileManager.default.removeItem(at: legacyDataURL.deletingLastPathComponent().appendingPathComponent("MyApp.sqlite-wal"))
} catch {
print("Error deleting persistent store at Application Support directory: \(error)")
}
}
}
}
NOTE: Widget extensions may create an App Group store before the migration could happen by opening the app. Therefore I'm replacing the persistent store at the App Group location.
Please note that this code doesn’t migrate to CloudKit yet!
Post
Replies
Boosts
Views
Activity
Do you have any attribute that uses an enum? I had the same issue and solved it by setting the attribute that is defined to an optional enum.
I finally figured it out by using the asset catalog to color the different sizes. Would be great if the HIG would clarify that in the first place!
So as of Xcode 16.0 beta 5 the sizes are used the following way:
64pt Size
Home Screen of iPhone Pro Max
iOS 18’s small size only
68pt Size
Home Screen of iPad Pro 13-inch
iOS 18’s small size only
Overview of all sizes
iOS 18’s Sizing
Even after the WWDC 24 announcements, I can’t find the sizes in the HIG specifications or anywhere else for that matter.
As of iOS 18 beta 1 and Xcode 16 beta 1 my measurements show that the new small and large icon modes use the following icon sizes:
iPhone 15 Pro
Normal/Small: 60pt
Large: 71pt
iPad Pro 11-inch
Normal/Small: 60pt
Large: 76pt
iPad Pro 13-inch
Normal/Small: 68pt
Large: 83.5pt
(The sizes are all in @1x size and apply for their respecting device size categories)
Interestingly the previous iPad icon sizes are now used for the large icon mode. The large mode on iPhone uses a totally new size, that we can’t even define directly.
At least that shows what the 68pt icon is used for (iPad Pro 13-inch small mode)! Which makes sense, because we can’t define a @3x size which is only available on iPhone.
Thanks for the improvements and also thanks a lot for updating us on those changes! Great to see that our feedback get’s valued. Keep it coming! 👏
[quote='756215021, Forums Product Manager, /thread/756215']
Apple Developer Relations and Apple engineering are joining forces to field your questions and work to solve your technical issues.
[/quote]
This is super exciting to hear! I (and I think many other fellow devs) highly appreciate your efforts to make this happen. Getting more direct help from Apple engineers will be such a great thing. I’m looking forward to it.
Thanks a ton for all those news and updates. Keep it coming! 🙌
After tons of experimenting (and help from Stack Overflow) I found the solution a few months ago.
This basically matches CryptoKoa’s answer above, so I’m accepting theirs. Just wanted to provide the slightly different code I’ve used regarding the different naming of the old Core Data model.
@main
struct MyApp: App {
let container: ModelContainer
init() {
// Legacy placement of the Core Data file.
let dataUrl = URL.applicationSupportDirectory.appending(path: "Model.sqlite")
do {
// Create SwiftData container with migration and custom URL pointing to legacy Core Data file.
container = try ModelContainer(
for: Foo.self, Bar.self,
migrationPlan: AppMigrationPlan.self,
configurations: ModelConfiguration(url: dataUrl))
} catch {
fatalError("Failed to initialize model container.")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
}
For people also struggling with a button styled Toggle(), Apple recommended in a Feedback to also place the .buttonStyle(.borderless) before the .toggleStyle(.button).
Unfortunately that doesn’t work on macOS for me. Neither of the other tricks helped either … but at least for iOS and visionOS it seems to work for me (running iOS 17.4).
I don’t think this is possible. There seems to be a private framework that Apple uses for Animoji called AvatarKit. There’s a project on GitHub that plays around with that very framework. Though, as they mention:
This project relies heavily on Apples private API and you should therefore not try to submit this code to App Store.
Apples support document, does not list the iPad Mini as supported devices (as of March 24, 2024):
iPhone 12 or later
iPad Air (5th generation)
iPad Pro 11-inch (3rd generation) or later
iPad Pro 12.9-inch (5th generation) or later
Mac with Apple silicon
In another thread, a user confirmed that the iPad Mini 6 is supported though …
Yes, that’s possible. Check out this great example on GitHub: https://github.com/mralexhay/Booky/blob/main/Shortcuts/Actions/OpenBook.swift
The code is basically the following:
// These will be the options in the Shortcut action to open a book or navigate to the library
enum NavigationType: String, AppEnum, CaseDisplayRepresentable {
case library
case book
// This will be displayed as the title of the menu shown when picking from the options
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Navigation")
static var caseDisplayRepresentations: [Self:DisplayRepresentation] = [
.library: DisplayRepresentation(title: "Library",
subtitle: "Return to the home page",
image: .init(systemName: "books.vertical")),
.book: DisplayRepresentation(title: "Book",
subtitle: "Navigate to a specific book",
image: .init(systemName: "book"))
]
}
struct OpenBook: AppIntent {
// Title of the action in the Shortcuts app
static var title: LocalizedStringResource = "Open Book"
// Description of the action in the Shortcuts app
static var description: IntentDescription = IntentDescription("This action will open the selected book in the Booky app or navigate to the home library.", categoryName: "Navigation")
// This opens the host app when the action is run
static var openAppWhenRun = true
// A dynamic lookup parameter
@Parameter(title: "Book", description: "The book to open in Booky", requestValueDialog: IntentDialog("Which book would you like to open?"))
var book: ShortcutsBookEntity
// An enum parameter
@Parameter(title: "Navigation", description: "Choose whether to open a book or navigate to Booky's library", default: .book, requestValueDialog: IntentDialog("What would you like to navigate to?"))
var navigation: NavigationType
// How the summary will appear in the shortcut action.
static var parameterSummary: some ParameterSummary {
Switch(\OpenBook.$navigation) {
Case(NavigationType.book) {
Summary("Open \(\.$navigation) \(\.$book)")
}
Case(NavigationType.library) {
Summary("Open \(\.$navigation)")
}
DefaultCase {
Summary("Open \(\.$navigation) \(\.$book)")
}
}
}
@MainActor // <-- include if the code needs to be run on the main thread
func perform() async throws -> some IntentResult {
do {
if navigation == .book {
let matchingBook = try BookManager.shared.findBook(withId: book.id)
ViewModel.shared.navigateTo(book: matchingBook)
} else {
ViewModel.shared.navigateToLibrary()
}
return .result()
} catch let error {
throw error
}
}
}
According to the Spotlight your app with App Shortcuts session (at 13:34) you need to add shortTitle and systemImageName to the AppShortcut that it appears in Spotlight.
AppShortcut(
intent: ShowTopDonutsIntent(),
phrases: [
"\(.applicationName) Trends for \(\.$timeframe)"
],
shortTitle: "Trends",
systemImageName: "chart.line.uptrend.xyaxis"
)
In the session they also say:
Note that if I want the entity shown at the top level in Spotlight or Shortcuts, my entities need to have an image or symbol in the display representation.
Though, to be fair I couldn’t get it to work for AppEnums – only for AppEntities. See my questions for more details.
Yes, the Navigator class is probably a singleton class here. Probably so that it can be used outside the apps main thread.
It’s basically an ObservableObject object that you can then use as @EnvironmentObject to bind a @Published variable for example to a NavigationStack(path: $navigator.somePath) or TabView(selection: $navigator.someView). This way you can control which view/navigation path the app should navigate to from outside any view. Quite useful for things like Shortcuts.
Code Example
Apple’s NavigationCookbook example uses this technique.
Though, another great example is Booky on GitHub! It’s a great source for the same concept here + generally how AppIntents work for Shortcuts.
Check out the specific places in code her:
Singleton Class
Binding in App View
Intent Action Use
As confirmed by an Apple engineer on the other post, Tip Kit is now finally available starting with Xcode Beta 5!
Check out the Xcode release notes for more information and known issues.
I noticed the same! The code the session uses at 6:58 isn’t working. The .modelContainer(for: [Trip.self, BucketListItem.self, LivingAccommodation.self]) code you’ve mentioned in the other comment is only for simple cases, and not really something to achieve the customization like yours …