[SwiftUI] @ObservedSectionedResults with onDelete Modifier: "remove" Method is not found

Hi, I am a Swift beginner developing a SwiftUI application using RealmSwift.
I am currently implementing a sectioned list using @ObservedSectionedResults and am trying to add the ability to swipe and delete items in the list. However, when I try to delete an item in a section, the app crashes with an out of bounds index error.

So I was researching this error and found that someone with the same problem as me posted an Issue on realm-swift GitHub with the same content.
GitHub Issue

Below is the code that the questioner provided that causes the error.
(The error occurs when method ‘delete(at offsets: section: )’ is executed.)

import SwiftUI
import RealmSwift

struct ContentView: View {
    @ObservedSectionedResults(Todo.self, sectionKeyPath: \.status) var todos
    
    func delete(at offsets: IndexSet, section: ResultsSection<String, Todo>){
        let deleteList = offsets.map { index in
            section[index]
        }
        let realm = try! Realm()
        try! realm.write {
            for todo in deleteList {
                realm.delete(todo)
            }
            
        }
    }
    
    var body: some View {
        VStack {
            List {
                ForEach(todos) { section in
                    Section(header: Text(section.key)) {
                        ForEach(section) { todo in
                            Text(todo.name)
                        }
                        .onDelete{
                            delete(at: $0, section: section)
                        }
                    }
                    
                }
            }
            Button("Add Ready Todo"){
                let realm = try! Realm()
                try! realm.write {
                    let todo = Todo(name: "New", ownerId: "123")
                    todo.status = "ready"
                    realm.add(todo)
                }
            }
            
            Button("Add Blocked Todo"){
                let realm = try! Realm()
                try! realm.write {
                    let todo = Todo(name: "New", ownerId: "123")
                    todo.status = "Blocked"
                    realm.add(todo)
                }
            }
        }
        .padding()
    }
}

import Foundation
import RealmSwift

class Todo: Object, ObjectKeyIdentifiable {
   @Persisted(primaryKey: true) var _id: ObjectId
   @Persisted var name: String = ""
   @Persisted var status: String = ""
   @Persisted var ownerId: String
   convenience init(name: String, ownerId: String) {
       self.init()
       self.name = name
       self.ownerId = ownerId
   }
}

I have rewritten ContentView based on this Issue.( removed the delete() method and modified the code inside the .onDelete modifier’s closure )

However,
" No exact matches in call to instance method ‘remove’ "
error occurred and method ‘remove’ could not be used.

Is the rewrite not correct?

Also, is there any other approach to remove a sectioned list by swiping through it line by line?

struct ContentView: View {
    @ObservedSectionedResults(Todo.self, sectionKeyPath: \.status) var todos
    
    var body: some View {
        VStack {
            List {
                ForEach(todos) { section in
                    Section(header: Text(section.key)) {
                        ForEach(section) { todo in
                            Text(todo.name)
                        }
                        .onDelete {
                            $todos.remove(atOffsets: $0, section: section) //error: No exact matches in call to instance method 'remove'
                        }
                    }
                    
                }
            }
            Button("Add Ready Todo"){
                let realm = try! Realm()
                try! realm.write {
                    let todo = Todo(name: "New", ownerId: "123")
                    todo.status = "ready"
                    realm.add(todo)
                }
            }
            
            Button("Add Blocked Todo"){
                let realm = try! Realm()
                try! realm.write {
                    let todo = Todo(name: "New", ownerId: "123")
                    todo.status = "Blocked"
                    realm.add(todo)
                }
            }
        }
        .padding()
    }
}

Welcome to the forums!

You code looks pretty solid and well formed - looks like your on your way to coding with Realm. I think you’ll enjoy it.

Are you wanting to delete the section entirely or the todos within that section? Did you try

.onDelete(perform: $todo.remove)

or like this

.onDelete(perform: { indexSet in
   todos.remove(atOffsets: indexSet)
}

I really appreciate you taking the time to look at this post.

I would like to delete one ToDo in a section at a time.

I would like to display each section in a list, and then delete one object in the section (in the case of the code you presented in the example, the ToDo) by sliding it and tapping the button.

However, the code you presented

.onDelete(perform: $todo.remove)
.onDelete(perform: { indexSet in
   todos.remove(atOffsets: indexSet)
}

The ‘remove()’ method is not available for ‘todos’, such as,
"No exact matches in call to instance method ‘remove’ "
error is displayed.

import SwiftUI
import RealmSwift

struct ContentView: View {
    @ObservedSectionedResults(Todo.self, sectionKeyPath: \.status) var todos
    
    func delete(at offsets: IndexSet, section: ResultsSection<String, Todo>){
        let deleteList = offsets.map { index in
            section[index]
        }
        let realm = try! Realm()
        try! realm.write {
            for todo in deleteList {
                realm.delete(todo)
            }
            
        }
    }
    
    var body: some View {
        VStack {
            List {
                ForEach(todos) { section in
                    Section(header: Text(section.key)) {
                        ForEach(section) { todo in
                            Text(todo.name)
                        }
                        .onDelete(perform: { indexSet in
                            $todos.remove(atOffsets: indexSet)
                        }) // An error occurs here🟥
                    }
                }
            }
            Button("Add Ready Todo"){
                let realm = try! Realm()
                try! realm.write {
                    let todo = Todo(name: "New", ownerId: "123")
                    todo.status = "ready"
                    realm.add(todo)
                }
            }
            
            Button("Add Blocked Todo"){
                let realm = try! Realm()
                try! realm.write {
                    let todo = Todo(name: "New", ownerId: "123")
                    todo.status = "Blocked"
                    realm.add(todo)
                }
            }
        }
        .padding()
    }
}

The versions of Xcode and Realm are as follows respectively.
Xcode: 15.0
Realm: 10.41.0

My apologies - got ahead of myself.

.onDelete(perform: { indexSet in
   todos.remove(atOffsets: indexSet)
}

should be

.onDelete(perform: { indexSet in
   let realm = ......
   guard let index = indexSet.first else { return }
   try realm.write {
      realm.delete(section[index])
   }
}

Thank you for all you are doing to try to resolve this issue.

I tried your code and got the same error as the realm-swift GitHub questioner who provided it in the beginning.
GitHub Issue

The error message
" Thread 1: “Index 2 is out of bounds (must be less than 2).” "

import SwiftUI
import RealmSwift

struct ContentView: View {
    @ObservedSectionedResults(Todo.self, sectionKeyPath: \.status) var todos
    
    func delete(at offsets: IndexSet, section: ResultsSection<String, Todo>){
        let deleteList = offsets.map { index in
            section[index]
        }
        let realm = try! Realm()
        try! realm.write {
            for todo in deleteList {
                realm.delete(todo)
            }
            
        }
    }
    
    var body: some View {
        VStack {
            List {
                ForEach(todos) { section in
                    Section(header: Text(section.key)) {
                        ForEach(section) { todo in
                            Text(todo.name)
                        }
                        .onDelete(perform: {indexSet in
                            let realm = try! Realm()
                            guard let index = indexSet.first else{ return }
                            try! realm.write {
                                realm.delete(section[index])
                            }
                        })
                    }
                }
            }
            Button("Add Ready Todo"){
                let realm = try! Realm()
                try! realm.write {
                    let todo = Todo(name: "New", ownerId: "123")
                    todo.status = "ready"
                    realm.add(todo)
                }
            }
            
            Button("Add Blocked Todo"){
                let realm = try! Realm()
                try! realm.write {
                    let todo = Todo(name: "New", ownerId: "123")
                    todo.status = "Blocked"
                    realm.add(todo)
                }
            }
        }
        .padding()
    }
}

There are many areas for improvement, though,
Instead of using @ObservedSectionedResults, I considered using @ObservedResults to get ‘todos’, then pass ‘todos’ filtered by ‘where’ to the ForEach initializer and delete them with the ‘delete()’ method I have also considered the approach of deleting them with the ‘delete()’ method.

With this code, I can reproduce the movement I was hoping for.

struct ContentView: View {
    @ObservedResults(Todo.self) var todos
    
    func delete(todo: Todo) {
        guard let thawedTodo = todo.thaw(),
              let realm = thawedTodo.realm else { return }
        
        do {
            try realm.write {
                realm.delete(thawedTodo)
            }
        } catch {
            print(error)
        }
    }
    
    var body: some View {
        VStack {
            List {
                Section(header: Text("READY")) {
                    ForEach(todos.where{ $0.status == "ready" }) { readyTodo in
                        Text(readyTodo.name)
                            .swipeActions(edge: .trailing) {
                                Button ("delete") {
                                    delete(todo: readyTodo)
                                }
                            }
                    }
                }
                Section(header: Text("BLOCKED")) {
                    ForEach(todos.where{ $0.status == "Blocked" }) { blockedTodo in
                        Text(blockedTodo.name)
                            .swipeActions(edge: .trailing) {
                                Button ("delete") {
                                    delete(todo: blockedTodo)
                                }
                            }
                    }
                }
            }
            Button("Add Ready Todo"){
                let realm = try! Realm()
                try! realm.write {
                    let todo = Todo(name: "New", ownerId: "123")
                    todo.status = "ready"
                    realm.add(todo)
                }
            }
            
            Button("Add Blocked Todo"){
                let realm = try! Realm()
                try! realm.write {
                    let todo = Todo(name: "New", ownerId: "123")
                    todo.status = "Blocked"
                    realm.add(todo)
                }
            }
        }
        .padding()
    }
}

Great! Does that mean the issue was resolved?

No it is not resolved.
I used @ObservedResults to reproduce what I wanted to do (swipe to delete ToDo separated by sections) as in my previous post.
But what I want to do is to use @ObservedSectionedResults to show ‘ToDo’ in the list by section and delete ‘ToDo’ by swiping a row in the list.

Hi, just to chime in, as I have the same crash

It seems like @ObservedSectionedResults is (at some point) not expecting a value to disappear from Realm. If I comment realm.delete(itemsToDelete) then there is no crash.

Here is my code:

struct CurrentTripView: View {

    @Environment(\.realmConfiguration) var realmConfiguration

    @ObservedSectionedResults(TripItem.self, sectionKeyPath: \.associatedPackID) var tripItems

    @State var trip: Trip

    var body: some View {
        List {
            ForEach(tripItems) { section in

                DisclosureGroup(packName(packId: section.key)) {
                    ForEach(section) { tripItem in
                        HStack {
                            TextField("Item name", text: ObservedRealmObject(wrappedValue: tripItem).projectedValue.name)
                        }
                    }
                    .onDelete(perform: { offsets in
                        guard let realm = try? Realm(configuration: realmConfiguration) else { return }

                        // Find items to delete
                        let itemsToDelete = offsets.map { section[$0] }

                        do {
                            try realm.write {
                                realm.delete(itemsToDelete)
                            }
                        } catch {
                            print("Error deleting items: \(error)")
                        } 
                    })
                }
            }
        }
    }

    func packName(packId: ObjectId?) -> String {
        if let packId,
           let realm = try? Realm(configuration: realmConfiguration),
           let name = realm.object(ofType: Pack.self, forPrimaryKey: packId)?.name
        {
            return name
        } else {
            return "no section"
        }
    }
}

and here is the stack trace:

*** Terminating app due to uncaught exception 'RLMException', reason: 'Index 1 is out of bounds (must be less than 1).'
*** First throw call stack:
(
	0   CoreFoundation                      0x0000000180491128 __exceptionPreprocess + 172
	1   libobjc.A.dylib                     0x000000018008412c objc_exception_throw + 56
	2   Realm                               0x000000010403a9c4 _Z27RLMThrowCollectionExceptionP8NSString + 140
	3   Realm                               0x0000000104044ed0 -[RLMSection objectAtIndex:] + 180
	4   RealmSwift                          0x0000000103250010 $s10RealmSwift14ResultsSectionVyq_Sicig + 108
	5   RealmSwift                          0x0000000103250c6c $s10RealmSwift14ResultsSectionVyq_Sicir + 88
	6   RealmSwift                          0x0000000103250bc8 $s10RealmSwift14ResultsSectionVyxq_GSlAASly7ElementQz5IndexQzcirTW + 72
	7   SwiftUI                             0x00000001c532c5b4 OUTLINED_FUNCTION_42 + 1004
	8   SwiftUI                             0x00000001c5334bd4 OUTLINED_FUNCTION_42 + 35340
	9   SwiftUI                             0x00000001c5335370 OUTLINED_FUNCTION_42 + 37288
	10  SwiftUI                             0x00000001c53352d8 OUTLINED_FUNCTION_42 + 37136
	11  SwiftUI                             0x00000001c5a94664 OUTLINED_FUNCTION_3 + 21080
	12  SwiftUI                             0x00000001c529ae08 OUTLINED_FUNCTION_11 + 16724
	13  SwiftUI                             0x00000001c529b114 OUTLINED_FUNCTION_11 + 17504
	14  SwiftUI                             0x00000001c529b074 OUTLINED_FUNCTION_11 + 17344
	15  SwiftUI                             0x00000001c5a94664 OUTLINED_FUNCTION_3 + 21080
	16  SwiftUI                             0x00000001c52f917c OUTLINED_FUNCTION_170 + 7572
	17  SwiftUI                             0x00000001c52f95e4 OUTLINED_FUNCTION_170 + 8700
	18  SwiftUI                             0x00000001c57dee40 OUTLINED_FUNCTION_3 + 19988
	19  SwiftUI                             0x00000001c57df014 OUTLINED_FUNCTION_3 + 20456
	20  SwiftUI                             0x00000001c57def34 OUTLINED_FUNCTION_3 + 20232
	21  SwiftUI                             0x00000001c4980f3c OUTLINED_FUNCTION_0 + 5260
	22  SwiftUI                             0x00000001c4981144 OUTLINED_FUNCTION_0 + 5780
	23  SwiftUI                             0x00000001c498108c OUTLINED_FUNCTION_0 + 5596
	24  libswiftCore.dylib                  0x0000000192a268e0 $ss16IndexingIteratorVyxGStsSt4next7ElementQzSgyFTW + 388
	25  libswiftCore.dylib                  0x0000000192b18350 $sSTsE21_copySequenceContents12initializing8IteratorQz_SitSry7ElementQzG_tF + 376
	26  libswiftCore.dylib                  0x00000001929e20c4 $ss32_copyCollectionToContiguousArrayys0dE0Vy7ElementQzGxSlRzlF + 516
	27  libswiftCore.dylib                  0x00000001929d18fc $sSTsE22_copyToContiguousArrays0cD0Vy7ElementQzGyFTm + 40
	28  libswiftCore.dylib                  0x00000001929d7434 $ss15ContiguousArrayVyAByxGqd__c7ElementQyd__RszSTRd__lufC + 32
	29  SwiftUI                             0x00000001c49808e8 OUTLINED_FUNCTION_0 + 3640
	30  SwiftUI                             0x00000001c55f5344 OUTLINED_FUNCTION_15 + 4616
	31  SwiftUI                             0x00000001c55f4308 OUTLINED_FUNCTION_15 + 460
	32  SwiftUI                             0x00000001c55f48f8 OUTLINED_FUNCTION_15 + 1980
	33  SwiftUI                             0x00000001c49c0348 OUTLINED_FUNCTION_0 + 32148
	34  SwiftUI                             0x00000001c49bce24 OUTLINED_FUNCTION_0 + 18544
	35  SwiftUI                             0x00000001c49b9714 OUTLINED_FUNCTION_0 + 4448
	36  SwiftUI                             0x00000001c49b9324 OUTLINED_FUNCTION_0 + 3440
	37  SwiftUI                             0x00000001c51e1fec OUTLINED_FUNCTION_7 + 1800
	38  SwiftUI                             0x00000001c50cbbe0 OUTLINED_FUNCTION_36 + 636
	39  SwiftUI                             0x00000001c497c0e8 OUTLINED_FUNCTION_13 + 12056
	40  SwiftUI                             0x00000001c5374abc __swift_allocate_boxed_opaque_existential_1Tm + 208
	41  AttributeGraph                      0x00000001b2bdc75c _ZN2AG5Graph11UpdateStack6updateEv + 504
	42  AttributeGraph                      0x00000001b2bdceec _ZN2AG5Graph16update_attributeENS_4data3ptrINS_4NodeEEEj + 432
	43  AttributeGraph                      0x00000001b2bea8dc _ZN2AG8Subgraph6updateEj + 828
	44  SwiftUI                             0x00000001c571f13c OUTLINED_FUNCTION_0 + 9900
	45  SwiftUI                             0x00000001c5720224 OUTLINED_FUNCTION_0 + 14228
	46  SwiftUI                             0x00000001c4f31934 OUTLINED_FUNCTION_0 + 35452
	47  SwiftUI                             0x00000001c57e5c18 OUTLINED_FUNCTION_12 + 11720
	48  SwiftUI                             0x00000001c57e4680 OUTLINED_FUNCTION_12 + 6192
	49  SwiftUI                             0x00000001c4f29d24 OUTLINED_FUNCTION_0 + 3692
	50  SwiftUI                             0x00000001c57201f0 OUTLINED_FUNCTION_0 + 14176
	51  SwiftUI                             0x00000001c5720128 OUTLINED_FUNCTION_0 + 13976
	52  SwiftUI                             0x00000001c51f742c OUTLINED_FUNCTION_21 + 32
	53  SwiftUI                             0x00000001c4985ed4 OUTLINED_FUNCTION_0 + 25636
	54  SwiftUI                             0x00000001c4985e58 OUTLINED_FUNCTION_0 + 25512
	55  SwiftUI                             0x00000001c4985f44 OUTLINED_FUNCTION_0 + 25748
	56  CoreFoundation                      0x00000001803f0ec4 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
	57  CoreFoundation                      0x00000001803eb8c8 __CFRunLoopDoObservers + 528
	58  CoreFoundation                      0x00000001803ebd80 __CFRunLoopRun + 968
	59  CoreFoundation                      0x00000001803eb5a4 CFRunLoopRunSpecific + 572
	60  GraphicsServices                    0x000000018e9fbae4 GSEventRunModal + 160
	61  UIKitCore                           0x00000001852f02e4 -[UIApplication _run] + 868
	62  UIKitCore                           0x00000001852f3f5c UIApplicationMain + 124
	63  SwiftUI                             0x00000001c51fc1b0 OUTLINED_FUNCTION_70 + 500
	64  SwiftUI                             0x00000001c51fc050 OUTLINED_FUNCTION_70 + 148
	65  SwiftUI                             0x00000001c4f02fa4 OUTLINED_FUNCTION_2 + 92
	66  Packs                               0x00000001007c4a24 $s5Packs0A3AppV5$mainyyFZ + 40
	67  Packs                               0x00000001007c4ad4 main + 12
	68  dyld                                0x0000000101a5d544 start_sim + 20
	69  ???                                 0x000000010155a0e0 0x0 + 4317356256
	70  ???                                 0x9c5f800000000000 0x0 + 11267865530192625664
)
libc++abi: terminating due to uncaught exception of type NSException

Some additional information may help. Is this a UI where the user swipes to delete one row?

What does offsets resolve to here

.onDelete(perform: { offsets in

and along those same lines, what does section[$0] resolve to here

let itemsToDelete = offsets.map { section[$0] }]

Hi Jay, thanks

Yes that’s right, .onDelete{} sets up the swipe gesture in SwiftUI. The parameter the closure receives is offsets: IndexSet , and mapping that produces: let itemsToDelete: [TripItem] = offsets.map { section[$0] } an array of TripItem (which itself is a RealmSwift.Object subclass).

I was more asking that, when you add a breakpoint in your code and step through it line but line do those resolve to an expected value?

In other words, this is your error

Index 1 is out of bounds (must be less than 1)

But we don’t know where the error s - e.g. this line crashes realm.delete(itemsToDelete) but we don’t know what itemsToDelete is - it backtracks to this

offsets.map { section[$0] }

so that means something is up with section[$0].

this line crashes realm.delete(itemsToDelete) but we don’t know what itemsToDelete is - it backtracks to this

offsets.map { section[$0] }

so that means something is up with section[$0].

That’s not quite correct: the entire onDelete{} code block runs as expected, including when stepping through with the debugger. Up to and including the delete, it all works. The crash is not on the deletion.

e.g. this line crashes realm.delete(itemsToDelete)

This line does show up in the stacktrace. The crash comes later, in a subsequent SwiftUI rendering that happens after the item is deleted. (indeed, removing that line` prevents the crash).

This line:
@ObservedSectionedResults(TripItem.self, sectionKeyPath: \.associatedPackID) var tripItems

is driving the UI (it is used by ForEach(tripItems){}). The crash happens as part of that (after the successful deletion has already occurred) because the deleted item is then missing from somewhere that @ObservedSectionedResults thought it would still be present. This indicates an issue inside @ObservedSectionedResults .

Parent frames in the stacktrace indicate that the crash is happening as part of the SwiftUI update cycle, somewhere inside Realm where it’s calling -[RLMSection objectAtIndex:] for an index that (no longer) exists.

(To answer your question though, I do get an valid array of [TripItem] from this line let itemsToDelete: [TripItem] = offsets.map { section[$0] } . So section[$0] is not an issue in this case.)