如果上下文发生更改,@FetchRequest谓词将被忽略



我有一个简单的SwiftUI应用程序,带有CoreData和两个视图。一个视图显示所有";放置";对象。您可以创建新位置,也可以显示该位置的详细信息。在第二个视图中,您可以添加";PlaceItem";去一个地方。

问题是,一旦一个新的";PlaceItem";添加到viewContext@NSFetchRequest似乎忘记了它的附加谓词,我在onAppear中设置了这些谓词。然后,每个位置项目都显示在详细信息视图中。一旦我手动更新谓词(刷新按钮(,只有选定位置的项才能再次显示。

知道怎么解决这个问题吗?以下是我的两个视图的代码:

struct PlaceView: View {
@FetchRequest(sortDescriptors: []) private var places: FetchedResults<Place>
@Environment(.managedObjectContext) private var viewContext

var body: some View {
NavigationView {
List(places) { place in
NavigationLink {
PlaceItemsView(place: place)
} label: {
Text(place.name ?? "")
}

}
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
let place = Place(context: viewContext)
place.name = NSUUID().uuidString
try! viewContext.save()
} label: {
Label("Add", systemImage: "plus")
}
}
}
.navigationTitle("Places")
}
}
struct PlaceItemsView: View {
@ObservedObject var place: Place
@State var searchText = ""
@FetchRequest(sortDescriptors: []) private var items: FetchedResults<PlaceItem>
@Environment(.managedObjectContext) private var viewContext
func updatePredicate() {
var predicates = [NSPredicate]()
predicates.append(NSPredicate(format: "place == %@", place))
if !searchText.isEmpty {
predicates.append(NSPredicate(format: "name CONTAINS %@", searchText))
}
items.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
}
var body: some View {
NavigationView {
List(items) { item in
Text(item.name ?? "");
}
}
.onAppear(perform: updatePredicate)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
let item = PlaceItem(context: viewContext)
item.place = place
item.name = NSUUID().uuidString
try! viewContext.save()
} label: {
Label("Add", systemImage: "plus")
}
}
ToolbarItem(placement: .navigationBarLeading) {
Button(action: updatePredicate) {
Label("Refresh", systemImage: "arrow.clockwise")
}
}
}
.searchable(
text: $searchText,
placement: .navigationBarDrawer(displayMode: .always),
prompt: "Search or add articles …"
)
.onAppear(perform: updatePredicate)
.onChange(of: searchText, perform: { _ in
updatePredicate()
})
.navigationTitle(place.name ?? "")
}
}
struct ContentView: View {
@Environment(.managedObjectContext) private var viewContext
var body: some View {
NavigationView {
PlaceView()
}
}
}

谢谢!

与其依赖onAppear,不如在PlaceItemView的初始值设定项中定义fetchRequest,例如:

struct PlaceItemsView: View {
@ObservedObject private var place: Place
@FetchRequest private var items: FetchedResults<PlaceItem>
init(place: Place) {
self.place = place
self._items = FetchRequest(
entity: PlaceItem.entity(),
sortDescriptors: [],
predicate: NSPredicate(format: "place == %@", place)
)
}

最好根据视图所需的数据对其进行分解,这称为具有更严格的无效性,还可以解决更新获取请求的问题!例如

struct PlaceItemsView {
@FetchRequest private var items: FetchedResults<PlaceItem>
var body: some View {
List(items) { item in
Text(item.name ?? "")
}
}
}
// body called when either searchText is different or its a different place (not when the place's properties change because no need to)
struct SearchPlaceView {
let searchText: String
let place: Place
var predicate: NSPredicate {
var predicates = [NSPredicate]()
predicates.append(NSPredicate(format: "place == %@", place))
if !searchText.isEmpty {
predicates.append(NSPredicate(format: "name CONTAINS %@", searchText))
}
return NSCompoundPredicate(type: .and, subpredicates: predicates)
}
var body: some View {
PlaceItemsView(items:FetchRequest<PlaceItem>(sortDescriptors:[], predicate:predicate))
}
}
// body called when either the place has a change or search text changes
struct PlaceItemsView: View {
@Environment(.managedObjectContext) private var viewContext
@ObservedObject var place: Place
@State var searchText = ""

var body: some View {
NavigationView {
SearchPlaceView(searchTest:searchText, place: place)
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
let item = PlaceItem(context: viewContext)
item.place = place
item.name = NSUUID().uuidString
try! viewContext.save()
} label: {
Label("Add", systemImage: "plus")
}
}
ToolbarItem(placement: .navigationBarLeading) {
Button(action: updatePredicate) {
Label("Refresh", systemImage: "arrow.clockwise")
}
}
}
.searchable(
text: $searchText,
placement: .navigationBarDrawer(displayMode: .always),
prompt: "Search or add articles …"
)
.navigationTitle(place.name ?? "")
}
}

总是试着用他们身体需要的最小数据来分解视图,尽量不要考虑用屏幕或屏幕区域来分解它们,这是一个非常常见的错误。

导入UIKit导入CoreData

类LoginViewController:UIViewController,UITableViewDataSource,UITableViewDelegate{

//MARK: - Outlets
@IBOutlet weak var LogInDataTableView: UITableView!
@IBOutlet weak var btnLogIn: UIButton!
@IBOutlet weak var btnAdd: UIButton!

//MARK: - Variables
var fetchedCoreData = [NSManagedObject]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var name = String()
let placeHolderArray = ["username", "password"]
var isLogin = false
//MARK: - Methods and other functionalities
override func viewDidLoad() {
super.viewDidLoad()

btnLogIn.layer.cornerRadius = 20.0
LogInDataTableView.layer.cornerRadius = 20.0
LogInDataTableView.layer.borderWidth = 2.0
LogInDataTableView.layer.borderColor = UIColor.blue.cgColor
let tap = UITapGestureRecognizer(target: self, action: #selector(UIInputViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
}
//MARK: - dismiss keyboard when tap on screen
@objc func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = true

fetchRequest()
}
//MARK: - fetch saved data from coredata
func fetchRequest() {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "EntityName")
request.returnsObjectsAsFaults = false

do {

let result = try context.fetch(request)
fetchedCoreData = result as! [NSManagedObject]
} catch {
print("error while fetch data from database")
}
}
//MARK: - login button click with validation
@IBAction func btnLogInClick(_ sender: Any) {
if loginData[0].isEmpty {
let alert = UIAlertController(title: "Alert", message: "Please enter username", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
} else if loginData[1].isEmpty {
let alert = UIAlertController(title: "Alert", message: "Please enter password", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
} else if  loginData[2].isEmpty{
let alert = UIAlertController(title: "Alert", message: "Please enter id", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
} else {

let registeredUser = clinicData.contains { objc in
objc.value(forKey: "username") as! String == loginData[0] && objc.value(forKey: "password") as! String == loginData[1]
}

if registeredUser == true {
for data in clinicData {
if data.value(forKey: "username") as! String == loginData[0] && data.value(forKey: "password") as! String == loginData[1] {
clinicName = data.value(forKey: "clinic") as! String
let addClinicVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "HomeScreenViewController") as! HomeScreenViewController
addClinicVC.clinicName = clinicName
self.navigationController?.pushViewController(addClinicVC, animated: true)
}
}

} else {
//MARK: - alert
let alert = UIAlertController(title: "Alert", message: "username and password mismatch", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
}


}

}
@IBAction func didTapAddClinic(_ sender: UIButton) {
let addVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NewScreenViewController") as! NewScreenViewController
self.navigationController?.pushViewController(addVC, animated: true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return placeHolderArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LoginTableViewCell", for: indexPath) as! LoginTableViewCell
cell.txtLoginData.tag = indexPath.row
cell.txtLoginData.placeholder = placeHolderArray[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}

}

最新更新