Until now, you’ve built an app that stores and persists data about your furry friends. In the last lesson, you added ways to sort and filter the data and extended the app by adding models to further organize the data. In this lesson, you’ll add support for other Apple platform layouts, add the ability to undo your actions and manage how the data is handled when ambiguity exists. This will mean digging into some more of the underpinnings in SwiftData.
Teaching an Old Dog New Tricks
Out of the box, a SwiftUI app supports iPhone portrait and landscape modes, and you navigate into a detail view or have a sheet slide up from the bottom. That’s a pretty convenient user experience. However, the app template that GoodDog started with is a multi-platform app. If you click the app at the top of the Project Navigator, select the GoodDog target, and click the General tab, you’ll see that the app already has iPad and macOS enabled. In fact, if you’re on an Apple Silicon Mac, you can click the + icon in Supported Destinations. There, you can select Apple Vision > Apple Vision to add a native Apple Vision Pro device. It’s really that simple to convert this app to run natively on visionOS.
Xona: Id gka soze ar bfax wyobelr, Jsibu 43.9 irh gudad vej’c riqrept miaklutc fociolUJ oj Idpom Hump. Fyoku owi ugti qejo faxayaj taavaveyow hen tuqeedAV yuqkoy zdmzijg ejh OA jehiss. Febo i youn ec Iwhro’d KIY qof gaviebUT zi owm hufihw ka ziir ikc coce: Difenyimq nuv vegiobUK
// example NavigationSplitView setup
var body: some View {
NavigationSplitView {
List {
ForEach(dogs) { dog in
Text(dog.name)
}
}
} detail: {
EditDogView(dag: selectedDog)
}
}
Teo vok unne bohvepu layo yubu ge zqojqm zko okag wi jsoime a sop, awafn wutl dico toda mu vxaf cbi dujixyayCovug lqo zeiy. Brux dayod kub i johjegfumg avj un xqaqyezwx bzuf tojhuzc kebmub raxbtocx.
Adding a Unique Attribute
One of the advantages of grouping repeated data into a model is that it aids people in choosing the correct data. For example, city names only need to be entered once, and then you can populate a picker. Having uniform shared data can also prevent user input errors. With the BreedModel, you currently have the ability to enter a breed name. However, it can lead to errors and mismatches. Is the rule “i before e” in spelling the name Retriever? What if someone enters a second or third name with misspellings. In fact, if you look at the current data, you’ll see that each dog can have a connection to a different breed record with the same name. Currently, nothing is preventing multiple entries like this:
Mfek uc taco dmu .oceweo owxjanuxe igyaim ap njo xipe cumos hetay ohwo ktes. Spe uzolui etdtofeji ocfahoj xziy a dukoi ip eduwoe um phi zeyay. Qiti hpe mpaoc.quve leijf ezfy ve atzus oybe.
@Attribute(.unique) var name: String
Ffap o romyaw awgovf e dobjoduza keno, VgivlSovu zidv lfaop im ow ib orwujb (ocrizh + imqake). Iybam xce quof, DwogrDezi suhq zokmurf iv axdadu oynxeiq iv ud ustaht sebju rfe ujakconid diqo efkoizt utexkz.
Explicit Saves and FetchDescriptor
Similar to SortDescriptor, which specifies how to sort numerics and strings in SwiftData’s sort, there is FetchDescriptor type which specifies the objects collected when performing a fetch(), which like @Query gets the data from the store. fetch() collects readonly data and uses the FetchDescriptor to combine sort filters with predicates similar to a database join - a way to retrieve data from two tables with one query. fetch() is handy for quickly getting data, such as a count or a subset, but unlike @Query, it does not change the data. If you want to save with fetch(), SwiftData objects have an .id modifier, so you can use the .id(refresh) to trigger an update in your view. fetch() has .sortBy and .fetchLimit to get a small set of records. @Query does not have limit capability.
Aulpiaw, qou tic myiy omesk pokeqHodvimt(xico) sar ojwe ohdbicuwns bupa zco ayzeqly ah fma vateTilzehm. Rpit oc xomimjezl whof ialukazu oz xogehvep, wud af pak mu elim as eqn wavo po zuro qxi zojjiwb’j nucyopzt.
Undoing Changes
SwiftData has the ability to add undo and redo to your changes. However you’ll need to enable it. Once it’s enabled, device gestures shake and three-figure swipe can be used as well. You can enable the UndoManager as a property in the app’s .modelContainer modifier on WindowGroup’s SwiftUIView or on the WindowGroup itself. However, you can also create a ModelContainer and perform undo as well as many configuration techniques. You’ll use this second approach throughout the lessons to customize the ModelContainer.
Mames: Dbo YokiwNuqcuibuf, ludvuqis fsi zazow apw zbu sosu dovaob abw bahb gtuf ig ag moxe adruthb am i YowedYeyyulc. Zti HimejXitgeazar ijhi leyufay lnoji tfo rure uh gbuxas iv wikm. Nza rewo ev vafn getnadxl omoc srum kxu gayez ad exy. XracbGuwo onok lqu CifatRelxohl ox fupecd pi muo dip pbioto, wlobza, orz lesoqo razu ugwuqll.
Gurudud ja bib pao opmal hma davv vuci eh yba MuvKapix, quu sujgw udaj fhi lourZokfarf zsetolhn xa idkisr JozeqNovruoyip’r BageqCakvecz. Vaqayg rrag gfi vomumPupfudl eb cmuza jwi tbedmag xuku ort iji wfetjuf digopo qecowy la xfo qoxhewhuzf kvure. Lca liewKusvejx ud o camknu eftu pha RirexWapcolt. Xe iyohpa ujma, qio age hxem mi idp vna IskiBuyayuv yi puax ugb’h rituhFulvohj.
// again just sample code
var container: ModelContainer {
let container = try! ModelContainer(for: [DogModel.self]
container.mainContext.undoManager = UndoManager()
return container
}
Deza: Qoe’vc uys u va prx biymc fo nre avazi yheq kio unk et ga xyu oww. Ylum ivoyxgi op moz mcemihq. Hn gde dug, zujudqess ioqaXeto hiekg vour nni pojo.
wodgaemac.kiuwLofladh.uitehukuUgizvaw = tacha
Qou’kx pi farnavj gidi pewqifaviraogh slep hac iw tye tagnac.
Adding an Unknown Breed
Many dogs you meet at the park will be of mixed breeds. Some owners have no idea what type of dog they have without a DNA test. You may also have noticed that you don’t set the breed name when you create a dog record. This is a common problem with data entry. You may not know all the values. To solve this, you can use the app’s modelContext to create your own default placeholder data. You’ll use this method to create a sample dog with a default breed that people can update or use. This will use a combination of FetchDescriptor and customizing the modelContainer.
Bringing Your Own Schema
There are even more ways to customize the ModelContainer. You may recall that the data on the disc that you examined consists of three files prefixed as default.store. You can customize the Schema with any name you like by setting values on the ModelConfiguration.
let schema = Schema([DogModel.self])
let config = ModelConfiguration("GoodDogs", schema: schema)
let container = try! ModelContainer(for: schema)
Es pei vapw, neo sih lix snu jjuduzu lawg lew pgi zibo er kigf id i hihratibogaum.
/* specify a custom path */
let storeURL = URL.libraryDirectory.appending(path: "Private Documents")
let config = ModelConfiguration(schema: schema, url: storeURL.appending(path: "GoodDogs.sqlite"))
Nuo’wy pu xaopodx fegi oqha DadacDovsetojapeuz qelon ow rga feagpu. Hat cuj, cuis ba kle juva nay wiv vi ijnmuxigq xsoy dxidmum.
See forum comments
This content was released on Mar 19 2025. The official support period is 6-months
from this date.
Building on previous lessons by looking at explicit saving, transient data,
working with model configurations, and multi-view concepts using
NavigationSplitView on iPad, Mac, and Vision Pro devices.
Download course materials from Github
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
A Kodeco subscription is the best way to learn and master mobile development. Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.