Think about the next minimal SwiftUI app demo that makes use of SwiftData:
App:
import SwiftUI
import SwiftData
@primary
struct SwiftData_Model_Repo_TestApp: App {
var physique: some Scene {
WindowGroup {
ContentView()
.modelContainer(for: Room.self)
}
}
}
SwiftData Fashions:
@Mannequin
last class Room {
var identify: String
var space: Double
var isSelected:Bool
init(identify: String, space: Double, isSelected: Bool) {
self.identify = identify
self.space = space
self.isSelected = isSelected
}
}
ContentView:
// for random String technology
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
struct ContentView: View {
@Question non-public var rooms: [Room]
@Atmosphere(.modelContext) var modelContext
@AppStorage("sliderValue") var sliderVal:Double = 0.5
var selectedRooms:[Room] {
var outcome:[Room] = []
for room in rooms {
if room.isSelected {
outcome.append(room)
}
}
return outcome
}
// this can be a perform of BOTH person enter (slider) AND selectedRooms
var totalHouseSize:Double {
var totalArea = 0.0
for room in selectedRooms {
totalArea += room.space
}
return (totalArea * sliderVal)
}
var physique: some View {
Spacer()
Textual content("Add a room").onTapGesture {
let randomString = String((0..<4).map{ _ in letters.randomElement()! })
let newRoom = Room(identify: "Room (randomString)", space: Double.random(in: 800...3000), isSelected: false)
modelContext.insert(newRoom)
}
Checklist{
ForEach(rooms, id: .self) { room in
HStack{
Textual content(room.identify)
Textual content("(Int(room.space))")
Spacer()
Circle()
.fill(room.isSelected ? Shade.black : Shade.white)
.body(width: 50, top: 50)
.overlay(
Circle()
.stroke(Shade.black, lineWidth: 3)
)
.onTapGesture {
withAnimation{
room.isSelected.toggle()
}
}
}
}
}
Spacer()
Textual content("home measurement multiplier: x (sliderVal)")
Slider(worth: $sliderVal, in: 1...100)
Spacer()
Textual content("complete home measurement will probably be: (totalHouseSize)")
}
}
The “rooms”/”home” state of affairs on this instance code is inconsequential/convoluted/simplistic for brevity.
The essential takeaways are:
- We now have a set of basic objects that we’re holding persistent utilizing SwiftData
- There may be doubtlessly very advanced logic related to this knowledge that’s used to formulate views
- This logic is a perform of ALL these “impartial variables”:
- person enter by way of two-way bindings (these values are additionally persistent)
- the set of chosen knowledge objects
- properties inside these particular person knowledge gadgets
So, as you’ll be able to see, on this instance, like all SwiftData examples I’ve seen, we “question” the info objects instantly from inside a view… and any “helper capabilities” we write additionally should exist inside that view. This will get messier the extra advanced issues grow to be.
Our choices for refactoring appear to be:
- Make a separate class filled with static helper capabilities (appears unhealthy)
- Make a separate struct that’s initialized utilizing all of the impartial variables concerned, and simply re-instantiate it each time the view is refreshed as a consequence of a state change (appears unhealthy)
- Try the MVVM sample in SwiftUI, which is mostly frowned upon lately, with solely semi-workable methods (appears not nice)
However what if we wish a singular “Primary Repo Class” like this one from the SwiftUI Landmarks tutorial:
@Observable
class ModelData {
var landmarks: [Landmark] = load("landmarkData.json")
var hikes: [Hike] = load("hikeData.json")
var profile = Profile.default
var options: [Landmark] {
landmarks.filter { $0.isFeatured }
}
var classes: [String: [Landmark]] {
Dictionary(
grouping: landmarks,
by: { $0.class.rawValue }
)
}
}
This doesn’t use SwiftData as a result of it wasn’t launched but, I consider. I feel this is called the “repository sample?” Anyway, it has the good thing about a single entry level to our “repository” and it encapsulates the related logic. If anybody is aware of the right software program design time period for this, please remark.
However like I stated, I’ve not seen any SwiftData samples the place there’s a “single occasion entry level” to the info like this.
I’ve managed to rustle up the next working refactor:
App:
import SwiftUI
import SwiftData
@primary
struct SwiftData_Model_Repo_TestApp: App {
var physique: some Scene {
WindowGroup {
TopLevelWrapperView()
.modelContainer(for: ModelRootInstance.self)
}
}
}
SwiftData Fashions:
[room model is the same]
@Mannequin
last class ModelRootInstance {
// that is our fundamental knowledge repo
var rooms:[Room]
var sliderVal:Double
var selectedRooms:[Room] {
var outcome:[Room] = []
for room in rooms {
if room.isSelected {
outcome.append(room)
}
}
return outcome
}
// this can be a perform of BOTH person enter (slider) AND selectedRooms
var totalHouseSize:Double {
var totalArea = 0.0
for room in selectedRooms {
totalArea += room.space
}
return (totalArea * sliderVal)
}
init(rooms: [Room], sliderVal: Double) {
self.rooms = rooms
self.sliderVal = sliderVal
}
}
TopLevelWrapper:
struct TopLevelWrapperView: View {
@Question non-public var repo: [ModelRootInstance]
@Atmosphere(.modelContext) var modelContext
var physique: some View {
VStack{
if !repo.isEmpty {
ContentView(repo: repo.first!)
} else {
Shade.pink
}
}.onAppear(carry out: {
if repo.isEmpty {
let _blah = ModelRootInstance(rooms: [], sliderVal: 1)
modelContext.insert(_blah)
}
})
}
}
ContentView:
// for random String technology
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
struct ContentView: View {
@Bindable var repo: ModelRootInstance
var physique: some View {
Spacer()
Textual content("Add a room").onTapGesture {
print("yup")
let randomString = String((0..<4).map{ _ in letters.randomElement()! })
let newRoom = Room(identify: "Room (randomString)", space: Double.random(in: 800...3000), isSelected: false)
repo.rooms.append(newRoom)
print("(repo.rooms.rely)")
}
Checklist{
ForEach(repo.rooms, id: .self) { room in
HStack{
Textual content(room.identify)
Textual content("(Int(room.space))")
Spacer()
Circle()
.fill(room.isSelected ? Shade.black : Shade.white)
.body(width: 50, top: 50)
.overlay(
Circle()
.stroke(Shade.black, lineWidth: 3)
)
.onTapGesture {
withAnimation{
room.isSelected.toggle()
}
}
}
}
}
Spacer()
Textual content("home measurement multiplier: x (repo.sliderVal)")
Slider(worth: $repo.sliderVal, in: 1...100)
Spacer()
Textual content("complete home measurement will probably be: (repo.totalHouseSize)")
}
}
What has modified within the refactor:
- creates a brand new @Mannequin Swiftdata class that serves because the “Primary Repo Class.”
- all different “swift knowledge” mannequin cases are a property of this class
- conditionally initializes the one occasion of this class if crucial and inserts it into a brand new “TopLevelWrapper” view that sits between app and ContentView and exists just for this goal. That is crucial as a result of you’ll be able to’t (apparently) entry SwiftData modelContext exterior of a view.
- the sliderValue is now not applied with app storage however as a property of the repo class
- all logic is within the repo class
- we now not want modelContext and act instantly on repo.rooms
I am undecided if this refactor is suitable/workable or god forbid even genious… or if it is only a terribly silly anti-pattern.
To keep away from being accused of asking a number of questions, I will put it as a flowchart like this:
Is there some identified purpose why this could not even be achieved/tried? If not, have I supplied the present defacto method? If not, then what’s one of the simplest ways to do it?