#swift
#быстрый
Вопрос:
привет, сообщество, я новичок, и это мой первый вопрос. как изменить все атрибуты объекта и иметь возможность изменять все мои основные элементы данных, потому что я могу изменить только первый атрибут объекта, но не все мои записи данных.
Здесь, в этой функции, я могу только изменить имя, а затем я получаю следующую ошибку: строка:
let objectUpdate = test[0] : Thread 1: Fatal error: Index out of range
func updateData() {
var newName = ""
var newPrenom = ""
newName = name.text!
newPrenom = prenom.text!
let managedContext = AppDelegate.viewContext
let fetchRequest : NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "name = %@", newName)
do {
fetchRequest.predicate = NSPredicate(format: "prenom = %@", newPrenom)
let test = try! managedContext.fetch(fetchRequest) as! [NSManagedObject]
let objectUpdate = test[0]
objectUpdate.setValue(newName,forKey: "name")
objectUpdate.setValue(newPrenom, forKey: "prenom")
do {
try managedContext.save()
}
catch {
print(error)
}
} catch {
print(error)
}
}
Комментарии:
1. Ошибка означает, что ваш запрос на выборку не вернул никаких значений. Вы должны сделать что-то вроде
if test.isEmpty { return }
перед строкой, которая выдает вам ошибку2. Можете ли вы опубликовать код своих
Person
свойств CoreDataProperties?
Ответ №1:
Существует несколько способов избежать этой ошибки.
Разворачивание необязательного .first
значения
Коллекция Swift дает нам безопасный способ получить первый элемент, просто обратившись к first
свойству в данной коллекции. Он вернет Optional<Element>
значение, поэтому нам нужно сначала развернуть его либо if let
с помощью guard let
if let object = test.first {
// do something with object
}
или
guard let object = test.first else { return }
// do something with object
Проверка наличия значения в индексе
Часто бывает хорошей идеей проверить наличие определенного индекса в indices
свойстве, прежде чем обращаться к значению, стоящему за ним.
if test.indices.contains(0) {
let object = test[0]
// do something with object
}
Эти подсказки должны предотвратить повторный сбой вашего кода.
Другие предложения
Это не совсем безопасно или чисто:
var newName = ""
var newPrenom = ""
newName = name.text!
newPrenom = prenom.text!
Мы можем сделать его намного чище и, самое главное, безопаснее, используя guard
инструкцию
guard let newName = name.text, let newPrenom = prenom.text else { return }
Здесь произошли две важные вещи:
- Больше не нужно принудительно разворачивать необязательные значения
text
[, которые могут привести к сбою] - Свойства теперь неизменяемы, что означает, что мы можем быть уверены, что то, что мы сохраняем в CoreDate, — это то, что было восстановлено в начале функции
С тех пор, как линия:
let test = try! managedContext.fetch(fetchRequest) as! [NSManagedObject]
уже заключен в do-catch
предложение, вы можете безопасно удалить forced try!
и заменить его try
.
let test = try managedContext.fetch(fetchRequest) as! [NSManagedObject]
Давайте использовать типы! В этой строке вы создаете NSFetchRequest
объект для некоторой сущности с именем "Person"
.
let fetchRequest : NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "Person")
Я предполагаю, что CoreData сгенерировал для вас NSManagedObject
подкласс с именем Person
. Если это правда, вы могли бы переписать его следующим образом:
let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
Реализовав предыдущий совет, теперь мы можем избавиться as! [NSManagedObject]
от этой строки:
let test = try managedContext.fetch(fetchRequest) as! [NSManagedObject]
Поскольку NSFetchRequest
объект теперь хорошо типизирован, мы можем воспользоваться этим, переписав его следующим образом:
let test: [Person] = try managedContext.fetch(fetchRequest)
Итак, теперь мы используем правильные типы? круто! Теперь давайте улучшим это:
objectUpdate.setValue(newName,forKey: "name")
objectUpdate.setValue(newPrenom, forKey: "prenom")
переписав это и используя свойства Person
объекта
objectUpdate.name = newName
objectUpdate.prenom = newPrenom
Нет необходимости вводить do-catch
пункт второго уровня, поскольку мы уже находимся в одном из них!
do {
try managedContext.save()
}
catch {
print(error)
}
вы можете легко заменить его просто save()
вызовом, вот так:
try managedContext.save()
Вы уверены, что эти предикаты — это то, что вам нужно?
fetchRequest.predicate = NSPredicate(format: "name = %@", newName)
fetchRequest.predicate = NSPredicate(format: "prenom = %@", newPrenom)
Что я могу прочитать из них, так это то, что вы извлекаете Person
объект, где есть имя newName
и prenom newPrenom
, а затем обновляете его с теми же точными значениями? Используете ли вы какую-то идентификацию пользователей? нравится id: Int
или id: UUID
? Было бы гораздо разумнее написать что-то вроде этого
let id: Int = // ID of the user you are currently editing
fetchRequest.predicate = NSPredicate(format: "id == (id)")
если вы не используете какие-либо идентификаторы, вы можете попробовать сохранить начальные значения name
и prenom
// in cell declaration - set when you configure your cell
var initialName: String?
var initialPrenom: String?
// then in your function:
fetchRequest.predicate = NSPredicate(format: "name = %@", initialName)
fetchRequest.predicate = NSPredicate(format: "prenom = %@", initialPrenom)
Но я только что заметил, что вы также переопределяете свой первый предикат вторым. Вам нужно использовать NSCompoundPredicate
fetchRequest.predicate = NSCompoundPredicate(
type: .and, subpredicates: [
NSPredicate(format: "name = %@", initialName),
NSPredicate(format: "prenom = %@", initialPrenom)
]
)
Предлагаемая версия
func updateData() {
guard let newName = name.text, let newPrenom = prenom.text else { return }
let managedContext = AppDelegate.viewContext
let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
fetchRequest.predicate = NSCompoundPredicate(
type: .and, subpredicates: [
NSPredicate(format: "name = %@", initialName),
NSPredicate(format: "prenom = %@", initialPrenom)
]
)
do {
let objects: [Person] = try managedContext.fetch(fetchRequest)
guard let object = objects.first else { return }
object.name = newName
object.prenom = newPrenom
try managedContext.save()
} catch {
print(error)
}
}
Комментарии:
1. Спасибо за вашу помощь, я внес небольшие изменения, но я могу изменить только имя и имя. Для всех моих элементов, сохраненных в моем TableView, я могу изменить только имя каждой ячейки
2. Я только что обновил ответ, ознакомьтесь с той частью, где я говорю о ваших предикатах. Для меня они не имеют особого смысла
3. Для идентификатора я не понят, но в остальном я попробовал, как вы мне точно указали, и в результате я могу просто изменить имя каждого человека для регистрации пример: я записал в: Имя: Копе Имя: Брайан Когда я запускаю обновление, я могу редактировать только Копе, а неБрайан. Поэтому я могу изменить только все имена каждого человека, а не все элементы
4. Давайте пока забудем об идентификаторе. Вы должны использовать старые имена в предикате, а не новые. Новые еще не сохранены, поэтому вы не сможете получить нужный вам объект таким образом. Попробуйте сохранить старое имя и prenom при настройке представления, а затем использовать эти значения для предиката.
5. Здравствуйте, извините, что снова возвращаюсь к вам, я попробовал несколько вещей. Вы правы в отношении предиката, из-за которого возникает ошибка. в итоге я не могу изменить имя не по имени. пример, если в моем представлении таблиц 15 человек. Я мог бы изменить имя всех этих людей, но я хочу изменить имя и фамилию каждого человека
Ответ №2:
Если индекс 0 находится вне диапазона, это означает, что массив пуст. Перед доступом к нему добавьте
if test.isEmpty{
return //the fetch request didn't return any values
}