When the following models in SwiftData,
@Model
final class UndoRedoData {
var id: [Int]
init(id: [Int]) {
self.id = id
}
}
I created the following code.
struct ContentView: View {
@ObservedObject var swiftDataViewModel = SwiftDataArrayViewModel.shared
@State private var idArray: [Int] = [1,2,3,4]
@State private var firstviewSwich: Bool = true
@State private var twoviewSwich: Bool = false
@State private var threeviewSwich: Bool = false
var body: some View {
VStack {
if firstviewSwich == true {
Button(action: addItem) {
Text("1.New Item")
}
}
if twoviewSwich == true {
Button {
forArrayData()
} label: {
Text("2.Data Road")
}
}
if threeviewSwich == true {
Button(action: undoItem) {
Text("3.Undo")
}
}
}
}
private func addItem() {
withAnimation {
let newItem = UndoRedoData(id: [1,2,3,4])
swiftDataViewModel.taskContext.insert(newItem)
do {
try swiftDataViewModel.taskContext.save()
} catch {
print(error)
}
swiftDataViewModel.fetchItems()
firstviewSwich.toggle()
twoviewSwich.toggle()
}
}
private func forArrayData() {
twoviewSwich.toggle()
for data in idArray {
swiftDataViewModel.idUndoCreate(id: data, undoManager: swiftDataViewModel.arrayItemUndoManager)
}
threeviewSwich.toggle()
}
private func undoItem() {
swiftDataViewModel.arrayItemUndoManager.undo()
threeviewSwich.toggle()
firstviewSwich.toggle()
}
}
class SwiftDataArrayViewModel: ObservableObject {
static let shared = SwiftDataArrayViewModel()
let modelContainer: ModelContainer
@ObservationIgnored
lazy var taskContext: ModelContext = {
return ModelContext(modelContainer)
}()
@Published var arrayItems = [UndoRedoData]()
@Published var arrayItemUndoManager = UndoManager()
init() {
let schema = Schema([UndoRedoData.self])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError(error)
}
fetchItems()
}
func fetchItems() {
let fetchDescriptor = FetchDescriptor<UndoRedoData>()
do {
arrayItems = try taskContext.fetch(fetchDescriptor)
} catch {
fatalError(error)
}
}
func idUndoCreate(id: Int, undoManager: UndoManager?) {
undoManager?.registerUndo(withTarget: self) { target in
target.removeID()
}
}
func removeID() {
if let firstUndoRedoData = arrayItems.first {
print("Before Delete:\(firstUndoRedoData.id)")
if !firstUndoRedoData.id.isEmpty {
firstUndoRedoData.id.removeLast()
}
print("After Delete:\(firstUndoRedoData.id)")
}
do {
try taskContext.save()
} catch {
print(error)
}
fetchItems()
}
}
In this code, 1. Create an Item in New Item, 2. Execute Data Road and register the data in the array that is the same value as the data created in New Item in SwiftData one by one in UndoManager by for data in idArray.
This is done because the data in the array and the data created by New Item in SwiftData can be known in advance.
private func forArrayData() {
twoviewSwich.toggle()
for data in idArray {
swiftDataViewModel.idUndoCreate(id: data, undoManager: swiftDataViewModel.arrayItemUndoManager)
}
// class SwiftDataArrayViewModel: ObservableObject
func idUndoCreate(id: Int, undoManager: UndoManager?) {
undoManager?.registerUndo(withTarget: self) { target in
target.removeID()
}
}
After registering in UndoManager, when Undo is executed with 3. Undo, instead of being able to perform Undo where one id is deleted each time, all the data of the id in SwiftData is deleted in a one-time Undo.
I would like to be able to delete one id each time Undo is performed and restore them in sequence, but I can only delete them all once. Does this mean that such registration to UndoManager should not be done with for statements, etc.? Or is there another problem in the code?
I want to make sure that one id is deleted for each Undo executed.
Post
Replies
Boosts
Views
Activity
Is it possible to track history using the new HistoryDescriptor feature in SwiftData? Or can I only get the current most recent data? Or is it possible to output the changed data itself, along with timestamps?
I am hoping that it is possible to track by a standard feature like NSPersistentHistoryTransaction in CoreData.
Do we still have to use a method in SwiftData that creates more tracking data itself?
I would like to convert two audio files to AVAudioPCMBuffer and then mix the AVAudioPCMBuffer and save it in m4a format, etc Is there any other way other than using AVAudioEngine?
I am writing code to be able to perform Undo&Redo with SwiftUI and SwiftData. Unlike usual, there is a Model of SwiftData in the Observable class,
#Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true, isUndoEnabled: true)
@Environment(\.undoManager) var undoManager
but it doesn't work. How should I describe the isUndoEnabled: true option and the undoManager?
import SwiftData
@Observable class SwiftDataCoordinator {
static let shared = SwiftDataCoordinator()
var items = [Item]()
let modelContainer: ModelContainer = {
let schema = Schema([
Item.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
}
Is it possible to do something with @Observable class to make it constantly monitored and updatable?
Using SwiftUI, the timecode (seconds notation) has been referenced using ObservableObject as follows. In this case, the timecode values were reflected in Text in real time without any problem.
struct ContentView: View {
.
.
.
var body: some View {
NavigationStack {
// Time Code Text
Text(String(format:"%02d:%02d:%02d", sMinute, sSecond, sMsec))
.font(Font(UIFont.monospacedDigitSystemFont(ofSize: 30.0, weight: .regular)))
class ViewModel: ObservableObject {
// Time Code "%02d:%02d:%02d"
@Published var sMinute = 0
@Published var sSecond = 0
@Published var sMsec = 0
When I changed it to @Observable class as follows, the timecode operation is stopped and the values are updated only when the operation is finished.
@Observable class ViewModel {
// Time Code "%02d:%02d:%02d"
var sMinute = 0
var sSecond = 0
var sMsec = 0
Is it possible to do something with the @Observable class that would allow it to be constantly monitored and updated in real time?
Or should we change it back?
If we have a history of changing to @Observable in relation to other functions, and we have no choice but to revert back, is this where we need to make a change that would keep it functional separately?
I am trying to pass array data in Uniform from Swift to Metal's fragment shader. I am able to pass normal Float numbers that are not arrays with no problem. The structure is as follows
struct Uniforms {
var test: [Float]
}
The values are as follows
let floatArray: [Float] = [0.5]
As usual, we are going to write and pass the following As mentioned above, normal Float values can be passed without any problem.
commandEncoder.setFragmentBytes(&uniforms, length: MemoryLayout<Uniforms>.stride, index: 0)
The shader side should be as follows
// uniform
struct Uniforms {
float test[1];
};
Fragment Shader
// in fragment shader
float testColor = 1.0;
// for statement
for (int i = 0; i < 1; i++) {
testColor *= uniforms.test[i];
}
float a = 1.0 - testColor;
return float4(1.0,0.0,0.0,a);
I thought that 0.5 in the array was passed, but no value is passed.
I think I am writing something wrong, but how should I write it?
Sorry for the rudimentary question, SwiftData question.
In the case of Xcode standard sample code,
@main
struct SwiftDataTestProjectsApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Department.self,.
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
and create a ModelContainer for the Scene, #Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true)
}
However, if SwiftData is not used in the ContentView, and only if the transition is made to the other view using NavigationLink, as shown in the code below, a ModelContainer is created and a ModelContainer is specified for the ContentTwoView, If I want to specify a separate container for ContentTwoView, how should I create .modelContainer(sharedModelContainer) and specify .modelContainer(for: Item.self, inMemory: true)? Should I continue to create the ModelContainer for the Scene as before? Or is there a way to create it only for the specified View and specify it there?
struct ContentView: View {
var body: some View {
NavigationStack {
NavigationLink("ContentTwoView") {
ContentTwoView())
}
}
}
}
struct ContentTwoView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item].
var body: some View {
Sorry for the elementary question.
In the following case in SwiftData, can I display the
results of the search and replace text = "Test" with text = "Test2"?
// Serch
let test = #Predicate<GroupItem> { testitem in
testitem.text.contains("Test")
}
I'm having trouble understanding the database because I can't replace the data that I searched for to see if the search was successful or not.
@Model
class Item {
var id: Int
var groupItem: [GroupItem]?
init(id: Int) {
self.id = id
}
}
@Model
class GroupItem {
var groupId: Int
var text: String
init(groupId: Int, text:String) {
self.groupId = groupId
self.text = text
}
}
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
var body: some View {
NavigationSplitView {
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.id)")
} label: {
Text("\(item.id)")
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
ToolbarItem {
Button(action: serchItems) {
Text("Serch")
}
}
}
} detail: {
Text("Select an item")
}
}
private func addItem() {
withAnimation {
let newItem = Item(id: 0 + 1)
let newItem2 = GroupItem(groupId: 0 + 1, text: "Test")
modelContext.insert(newItem)
modelContext.insert(newItem2)
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(items[index])
}
}
}
private func serchItems() {
// Serch
let test = #Predicate<GroupItem> { testitem in
testitem.text.contains("Test")
}
}
}
#Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true)
.modelContainer(for: GroupItem.self, inMemory: true)
}
How can I insert the following code into the database to add to an array in SwiftData and delete at the specified point?
struct DataModel: Identifiable, Codable {
var data: [[String]]
}
struct ContentView: View {
@State var dataArray: [DataModel] = []
var body: some View {
VStack {
Button("Add Data") {
dataArray[0].data.append(["Add Data"])
}
Button("Add Data1") {
dataArray[0].data[0].append(["Add Data1"])
}
Button("Remove Data") {
dataArray[0].data[0].remove(at: 0)
}