2015-05-18 46 views
1

我試圖從表視圖禁止NSManagedObject。從tableView刪除NSManagedObject並正確地重新加載視圖

我有一個TableView控制器的子類UITableViewController,並擁有一個類型爲myContainer的附加屬性以及一個項目數組。

@property (nonatomic, strong) myContainer * parentContainer; 
@property(nonatomic, strong) NSArray * allItems; 

myContainer中是一個NSManaged對象,它包含類型myItem的對象的NSSet *:

@interface myContainer : NSManagedObject 
@property (nonatomic, strong) NSSet *subItems; 

我已經實現刪除功能是這樣的:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { 
if (editingStyle == UITableViewCellEditingStyleDelete) { 

    NSManagedObjectContext *context = [[Catalog sharedCatalog] managedObjectContext]; 
    myItem *selectedItem = [self.parentContainer.subItems allObjects][indexPath.row]; 
    [context deleteObject:selectedItem]; 
    [self viewWillAppear:NO]; 
    [self.view setNeedsDisplay]; 
} 

}

(請注意,指令 [ t deleteObject:selectedItem] 給我一個警告「將'myObject *'發送給類型爲'NSManagedObject '的參數的不兼容指針類型;但我懷疑這是有關什麼如下)

方法viewWillAppear中是這樣的:

- (void)viewWillAppear:(BOOL)animated { 
    self.allItems = [self.parentContainer.subItems allObjects]; 
    NSLog(@"%lu objects",(unsigned long)[self.allItems count]); 
    [super viewWillAppear:animated]; 
    [self.tableView reloadData]; 
} 

對於理解發生了什麼我也子類的cellForRowAtIndexPath目的

- (UITableViewCell *)tableView:(UITableView *)tableView 
    cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    // .. // 
    myItem * selectedItem = self.allItems[indexPath.row]; 
    NSLog(@"%lu : %@",indexPath.row,[selectedItem description]); 
} 

現在假設我首先加載我的表,它包含3個對象。我收到日誌:

3 objects 
0 : item0 
1 : item1 
2 : item2 

我刷卡並刪除第二項。視圖更新但對應的單元不會消失;它只是變空了。日誌內容是這樣的:

3 objects 
0 : item0 
1 : (null) 
2 : item2 

現在我做任何事情,這將重新加載的觀點:要麼去上一個視圖,然後回來,去進一步的看法和回來,或刪除第二個對象。然後,一切都很好,如預期的日誌:

2 objects 
0 : item0 
1 : item2 

我所看到的,似乎密切相關的以下兩個問題,但他們沒有收到這似乎解決該問題的任何答案:tablerow delete but still showon in table viewcommitEditingStyle: delete data but continue to display row

我也知道有一個更好的方式來抑制細胞調用[tableView deleteRowsAtIndexPath ..](它在這裏工作),但我真的很想理解我得到的輸出和coreData對我後面的對象做的事情回...

我無法理解這裏發生的事情。我在前面的代碼上嘗試了幾個變體,但似乎沒有解決我的問題。

+0

將刪除規則更改爲Cascade。取消僅清除參考。 – Erakk

+0

[context deleteObject]之後,你會[上下文保存]嗎?刪除的對象只會標記爲已刪除,直到您保存上下文爲止。 – CSmith

+0

不,我不這樣做,但爲什麼我改變視圖並回來修改行爲?我的意思是我永遠不會在這個例子中保存。 – vib

回答

2

從我所看到的,我想myContainer中有一對多與myItem的關係。這很好,因爲核心數據會在刪除myItems對象後自動從該關係中刪除對象,但不是立即。不像一些人在這裏所說的,你不必爲這些變化保存上下文或刷新任何關係,你只需要在刪除後的上下文中調用processPendingChanges,如下所示:

[context deleteObject:selectedItem]; 
[context processPendingChanges]; 

processPendingChanges將導致核心數據做任何關係更新,它仍然需要做,並且還會觸發任何可能仍在等待處理的核心數據相關通知。

當核心數據不會釋放已刪除的對象或將它們從普通數組中移除時,您還需要確保在發生這些更改(您目前看起來並未這樣做)時重新填充您的allItems數組。它會將其標記爲已刪除,並使其佔用陣列中的空間。與Erakk在他的回答中所說的相似,你應該將這個數組重新填充到與你的表視圖重新加載相同的方法中。您也可以在這裏調用processPendingChanges,而不是在刪除之後,以確保在將對象從關係中拉出之前,任何和所有待處理的更改都反映在關係中。您不需要手動更新myContainer中的subItems關係。這是Core Data的工作。

- (void)reloadTableView { 
    [self.parentContainer.managedObjectContext processPendingChanges]; 
    self.allItems = [[NSMutableArray alloc] initWithArray:[self.parentContainer.subItems allObjects]]; 
    [self.tableView reloadData]; 
} 

正如一些人所說的,你絕對不希望被直接調用viewWillAppear:。你應該把你的代碼移到一個從兩個地方調用的助手。

這條線也是一個潛在的問題:

myItem *selectedItem = [self.parentContainer.subItems allObjects][indexPath.row]; 

subItems無序,一對多的關係,這意味着你不能保證讓你真的希望出來的項目數組,因爲NSSet上的「allItems」方法理論上可以以任何順序返回。您應該將這些對象存儲在(大概排序的)數組中,然後訪問它們,或者可以將您的關係更改爲有序關係,然後將該屬性更改爲NSOrderedSet。

希望這會有所幫助!

+0

感謝您的回答,它非常好,解決了我的問題(processPendingChanges部分)。所以我假設processPendingChanges在某些時候在不同控制器之間移動時被調用? – vib

+0

我認爲它應該在刪除之後被調用,而不是同步。但我可能是錯的。保存上下文也會觸發它。 –

+0

保存上下文不會在任何情況下觸發它。 'processPendingChanges'是告訴上下文處理用戶事件的調用站點。使用'performBlock:'在上下文中執行操作將導致處理用戶事件,'performBlockAndWait:'不會 - 因爲很好的原因。處理用戶事件不安全或將上下文置於未定義狀態的條件很多。這就是爲什麼直接調用'processPendingChanges'往往不是最好的建議。 – quellish

0

對我來說,它看起來像你沒有更新數據數組。您刪除了第1項,但該數組仍然具有對該NSManagedObject的引用,該引用可能被coreData標記爲「已刪除」。發生這種情況是因爲您設置數組的唯一部分代碼位於viewWillAppear上。

相反,每當刪除一個項目然後重新加載數據時,您應該重新獲取數組。

這也是每當你回到上一個/進一步的視圖並回來時,它的工作原因。因爲viewWillAppear正在被調用並更新數組。

UPDATE:

在我看來,你從你的parentContainer的對象。這可能意味着parentContainer需要知道您刪除了一個項目以更新陣列。我建議你創建一個委託,發送一個通知給它,或者只是在你的parentContainer中創建一個公共方法,並讓這個viewController在你重新加載tableView時調用。

我會做的方法是封裝重新加載的tableView的邏輯:

- (void)reloadTableView { 
    [self.parentContainer reloadSubItems]; 
    self.allItems = [self.parentContainer.subItems allObjects]; 
    [self.tableView reloadData]; 
} 

然後更改viewWillAppear中調用它:

- (void)viewWillAppear:(BOOL)animated { 
    [super viewWillAppear:animated]; 
    [self reloadTableView]; 
} 

,取而代之的顯式調用viewWillAppear中的commitEditingStyle:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { 
    if (editingStyle == UITableViewCellEditingStyleDelete) { 
     NSManagedObjectContext *context = [[Catalog sharedCatalog] managedObjectContext]; 
     myItem *selectedItem = [self.parentContainer.subItems allObjects][indexPath.row]; 
     [context deleteObject:selectedItem]; 
     [self reloadTableView]; 
     //you dont need to call setNeedsDisplay here to update the 
     //tableView unless you do other stuff related to view frames 
     //and the like, for example, you changed the tableView's 
     //frame. in this case you would call setNeedsLayout. 
    } 
} 

您會在喲中創建reloadSubItems你的parentContainer並在其中添加提取邏輯。

更新2:

,而不是編輯parentContainer的,你可以只刪除該項目從數組被刪除。首先,您需要將allItems更改爲NSMutableArray而不是NSArray

您reloadTableView方法是這樣的:

- (void)reloadTableView { 
    [self.parentContainer reloadSubItems]; 
    self.allItems = [[NSMutableArray alloc] initWithArray:[self.parentContainer.subItems allObjects]]; 
    [self.tableView reloadData]; 
} 

和commitEditingStyle:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { 
    if (editingStyle == UITableViewCellEditingStyleDelete) { 
     NSManagedObjectContext *context = [[Catalog sharedCatalog] managedObjectContext]; 
     myItem *selectedItem = [self.parentContainer.subItems allObjects][indexPath.row]; 
     [self.allItems removeObject:selectedItem]; 
     [context deleteObject:selectedItem]; 
     [self reloadTableView]; 
    } 
} 

這樣,你不需要告訴parentContainer更新項目,因爲你會從數組中刪除該項目。

更新3:

根據documentationdeleteObjects:,「當改變被提交,對象將從uniquing表格中刪除。如果對象尚未保存到持久性存儲,它只是從刪除收件人。」

所以,你需要保存上下文爲您刪除被提交到存儲,否則對象將只被標記爲「已刪除」

+0

但我明確地在我的函數commitEditingStyle中調用viewWillAppear。 僅當我更改視圖時,coreData是否有可能有效地刪除對象?那麼我想這與我觀察到的一致。那麼你能更具體一點關於我應該'重新獲取數組然後重新加載數據'嗎?非常感謝 ! – vib

+0

你不應該在任何地方顯式調用viewWillAppear,這是系統爲你調用的方法,你只能覆蓋:)。 我會更新我的答案。 – Erakk

+0

感謝您的延伸答案。我知道我的編程沒有遵循良好的做法,但我懷疑關鍵點確實是這個reloadSubItems:我需要寫什麼? – vib

相關問題