HomeiOS Developmentios - Tips on how to use Repository Sample with SwiftData?

ios – Tips on how to use Repository Sample with SwiftData?


Think about the next minimal SwiftUI app demo that makes use of SwiftData:

App:

import SwiftUI
import SwiftData

@fundamental
struct SwiftData_Model_Repo_TestApp: App {
    
    var physique: some Scene {
        WindowGroup {
            ContentView()
                .modelContainer(for: Room.self)
        }
    }

}

SwiftData Fashions:

@Mannequin
ultimate class Room {
    var title: String
    var space: Double
    var isSelected:Bool
    
    init(title: String, space: Double, isSelected: Bool) {
        self.title = title
        self.space = space
        self.isSelected = isSelected
    }
    
}

ContentView:

// for random String technology
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

struct ContentView: View {
    
    @Question personal var rooms: [Room]
    @Surroundings(.modelContext) var modelContext
    
    @AppStorage("sliderValue") var sliderVal:Double = 0.5
    
    var selectedRooms:[Room] {
        var end result:[Room] = []
        for room in rooms {
            if room.isSelected {
                end result.append(room)
            }
        }
        return end result
    }
    
    // it is a operate 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(title: "Room (randomString)", space: Double.random(in: 800...3000), isSelected: false)
            modelContext.insert(newRoom)
        }
        Record{
            ForEach(rooms, id: .self) { room in
                HStack{
                    Textual content(room.title)
                    Textual content("(Int(room.space))")
                    Spacer()
                    Circle()
                        .fill(room.isSelected ? Colour.black : Colour.white)
                        .body(width: 50, top: 50)
                        .overlay(
                            Circle()
                                .stroke(Colour.black, lineWidth: 3)
                        )
                        .onTapGesture {
                            withAnimation{
                                room.isSelected.toggle()
                            }
                        }
                }
            }
        }
        Spacer()
        Textual content("home dimension multiplier: x (sliderVal)")
        Slider(worth: $sliderVal, in: 1...100)
        Spacer()
        Textual content("complete home dimension will probably be: (totalHouseSize)")
        
    }
}

The “rooms”/”home” situation 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’s probably very complicated logic related to this information that’s used to formulate views
  • This logic is a operate of ALL these “impartial variables”:
    • person enter by way of two-way bindings (these values are additionally persistent)
    • the set of chosen information objects
    • properties inside these particular person information gadgets

So, as you may see, on this instance, like all SwiftData examples I’ve seen, we “question” the info objects straight from inside a view… and any “helper capabilities” we write additionally should exist inside that view. This will get messier the extra complicated issues change into.

Our choices for refactoring appear to be:

  1. Make a separate class stuffed with static helper capabilities (appears dangerous)
  2. 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 dangerous)
  3. Try the MVVM sample in SwiftUI, which is mostly frowned upon nowadays, with solely semi-workable strategies (appears not nice)

However what if we would like a singular “Essential 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 believe this is named the “repository sample?” Anyway, it has the advantage of 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

@fundamental
struct SwiftData_Model_Repo_TestApp: App {
    
    var physique: some Scene {
        WindowGroup {
            TopLevelWrapperView()
                .modelContainer(for: ModelRootInstance.self)
        }
    }

}

SwiftData Fashions:

[room model is the same]

@Mannequin
ultimate class ModelRootInstance {
    
    // that is our primary information repo
    var rooms:[Room]
    
    var sliderVal:Double
    
    var selectedRooms:[Room] {
        var end result:[Room] = []
        for room in rooms {
            if room.isSelected {
                end result.append(room)
            }
        }
        return end result
    }
    
    // it is a operate 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 personal var repo: [ModelRootInstance]
    @Surroundings(.modelContext) var modelContext
    
    var physique: some View {
        VStack{
            if !repo.isEmpty {
                ContentView(repo: repo.first!)
            } else {
                Colour.purple
            }
        }.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(title: "Room (randomString)", space: Double.random(in: 800...3000), isSelected: false)
            repo.rooms.append(newRoom)
            print("(repo.rooms.depend)")
        }
        Record{
            ForEach(repo.rooms, id: .self) { room in
                HStack{
                    Textual content(room.title)
                    Textual content("(Int(room.space))")
                    Spacer()
                    Circle()
                        .fill(room.isSelected ? Colour.black : Colour.white)
                        .body(width: 50, top: 50)
                        .overlay(
                            Circle()
                                .stroke(Colour.black, lineWidth: 3)
                        )
                        .onTapGesture {
                            withAnimation{
                                room.isSelected.toggle()
                            }
                        }
                }
            }
        }
        Spacer()
        Textual content("home dimension multiplier: x (repo.sliderVal)")
        Slider(worth: $repo.sliderVal, in: 1...100)
        Spacer()
        Textual content("complete home dimension will probably be: (repo.totalHouseSize)")
        
    }
}

What has modified within the refactor:

  • creates a brand new @Mannequin Swiftdata class that serves because the “Essential Repo Class.”
  • all different “swift information” mannequin situations are a property of this class
  • conditionally initializes the only occasion of this class if obligatory and inserts it into a brand new “TopLevelWrapper” view that sits between app and ContentView and exists just for this objective. That is obligatory as a result of you may’t (apparently) entry SwiftData modelContext outdoors of a view.
  • the sliderValue is now not carried out 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 straight on repo.rooms

I am unsure 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 am going to put it as a flowchart like this:

Is there some recognized cause why this could not even be achieved/tried? If not, have I supplied the present defacto strategy? If not, then what’s one of the best ways to do it?

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments