iOS Programming Editing UITableView
1.1 Editing mode
UITableView has an editing property, and when this property is set to YES, the UITableView enters editing mode.
UITableVIew有一个editing property,当属性设置为YES时,UITableView设置为editing mode。
Depending on how the table view is configured, the user can change the order of the rows, add rows, or remove rows. Editing mode does not allow the user to edit the content of a row.
根据tableview 如何配置,用户可以改变row的order,添加row,移除row,但是编辑模式却不能允许增加行。
That the table view uses the word "header" in two different ways: There can be a table header and there can be section headers. Likewise, there can be a table footer and section footers.
table view 有table header 和section header。类似的也有table footer 和section footer。
Notice that headerView is a strong property. This is because it will be a top-level object in the XIB file; you use weak references for objects that are owned (directly or indirectly) by the top-level objects。
注意到你使用了strong 属性。那是因为headerView 将是xib文件的最上层。我们使用弱引用指向属于最上层的对象。
XIB files are typically used to create the view for a view controller, but they can also be used any time you want to lay out view objects, archive them, and have them loaded at runtime.
xib文件一般用来为view controller 创建新的view。但是他们也可以用在任何你像lay out view object,获取他们,在运行时得到他们。
step 1
Create a new file (Command-N). From the iOS section, select User Interface, choose the Empty template, and click Next
Drag a UIView onto the canvas. Then drag two instances of UIButton onto that view. You will then want to resize the UIView so that it just fits the buttons; however, Xcode will not let you: the size is locked. To unlock the size, select the UIView on the canvas and open the attributes inspector. Under the Simulated Metrics section, select None for the Size option.
To load a XIB file manually, you use NSBundle.
为了手动加载XIB 文件,你需要NSBundle。
This class is the interface between an application and the application bundle it lives in.
这个类是application和application bundle 的接口。
When you want to access a file in the application bundle, you ask NSBundle for it.
当你需要从application bundle 中获取一个文件时,需要从NSBundle中获取。
An instance of NSBundle is created when your application launches, and you can get a pointer to this instance by sending the message mainBundle to NSBundle.
一个NSBundle的实例在应用启动的时候就已经创建了。你可以通过发送一个消息mainBundle 给NSBundle 获取一个指向它的指针。
Once you have a pointer to the main bundle object, you can ask it to load a XIB file.
一旦你获取了指向main bundle 的指针,你就可以要求它加载一个XIB文件了。
Notice that this is a getter method that does more than just get.
注意这个getter方法又不仅仅是一个get方法。
This is a common pattern: Lazy Instantiation puts off creating the object until it is actually needed.
这是一个常用的模式:延迟实例化,只有当它确实需要的时候才实例化该对象。
In some cases this approach can significantly lower the normal memory footprint of your app.
在一些情况下这确实能降低你的内存。
- (UIView *)headerView
{
// If you have not loaded the headerView yet... if (!_headerView) {
// Load HeaderView.xib
}
[[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self
options:nil];
return _headerView; }
notice that you passed self as the owner of the XIB file. This ensures that when the main NSBundle is parsing the resultant NIB file at runtime, any connections to the File's Owner placeholder will be made to that BNRItemsViewController instance.
你也注意到传递self 给xib 文件的owner。这确保了当main NSBundle 解析NIB文件的结果时,任何连接到FIle's owner placeholder的都将在BNRItemsViewController 实例。
UIView *view=self.headerView;
[self.tableView setTableHeaderView:view];
In addition, any object can load a XIB file manually by sending the message loadNibNamed:owner:options: to the application bundle.
任何想加载xib 文件的对象可以发送一个loadNibNamed:owner:options:给application bundle。
UIViewController's default XIB loading behavior uses the same code.
The only difference is that it connects its view outlet to the view object in the XIB file.
唯一不同的区别是它在XIB 文件里 连接view outlet 到view object .
Imagine what the default implementation of loadView for UIViewController probably looks like:
- (void)loadView
{
// Which bundle is the NIB in?
// Was a bundle passed to initWithNibName:bundle:?
NSBundle *bundle = [self nibBundle]; if (!bundle) {
// Use the default
bundle = [NSBundle mainBundle]; }
// What is the NIB named?
// Was a name passed to initWithNibName:bundle:? NSString *nibName = [self nibName];
if (!nibName) {
// Use the default
nibName = NSStringFromClass([self class]); }
// Try to find the NIB in the bundle
NSString *nibPath = [bundle pathForResource:nibName
ofType:@"nib"];
// Does it exist? if (nibPath) {
// Load it (this will set the view outlet as a side-effect
[bundle loadNibNamed:nibName owner:self options:nil]; } else {
// If there is no NIB, just create a blank UIView
self.view = [[UIView alloc] init]; }
}
1.1.2 Now let's implement the toggleEditingMode: method.
现在实现 toogleEditingMode方法。
You could toggle the editing property of UITableView directly.
你可以直接改变UITableView 的 editing property 。
However, UITableViewController also has an editing property.
然而UITableViewController仍然有一个editing property。
A UITableViewController instance automatically sets the editing property of its table view to match its own editing property.
UITableViewController 实例自动设置table view 的editing property 属性来匹配自己的属性。
To set the editing property for a view controller, you send it the message setEditing:animated:
为了设置view controller 的editing property ,你给它发送一个setEditing:animated的消息。
- (IBAction)toggleEditingMode:(id)sender
{
// If you are currently in editing mode... if (self.isEditing) {
// Change text of button to inform user of state
[sender setTitle:@"Edit" forState:UIControlStateNormal];
// Turn off editing mode
[self setEditing:NO animated:YES]; } else {
// Change text of button to inform user of state
[sender setTitle:@"Done" forState:UIControlStateNormal];
// Enter editing mode
[self setEditing:YES animated:YES]; }
}
1.2 Adding rows
There are two common interfaces for adding rows to a table view at runtime.
(1)A button above the cells of the table view.
table view 的cell 上的button。
This is usually for adding a record for which there is a detail view.
为了给一个记录添加一些细节。
For example, in the Contacts app, you tap a button when you meet a new person and want to take down all their information.
例如,在联系人app中,你点击一个button当你遇见一个新的人并想记录所有它的信息。
(2)A cell with a green plus sign.
一个有绿色加号的cell。
This is usually for adding a new field to a record, such as when you want to add a birthday to a person's record in the Contacts app.
Ultimately, it is the dataSource of the UITableView that determines the number of rows the table view should display.
最终是UITableView的datasource 决定了到底有多少行要显示。
After inserting a new row, the table view has six rows (the original five plus the new one).
table view添加一行后就变成了六行。
Then, it runs back to its dataSource and asks it for the number of rows it should be displaying.
然后就去datasource 需找它到底需要显示多少行。
BNRItemsViewController consults the store and returns that there should be five rows. The UITableView then says, "Hey, that is not right!" and throws an exception.
就会返回给UITableView说是有5行。这时就产生了不对了。UITableView就说不对啊。 就抛出了异常。
You must make sure that the UITableView and its dataSource agree on the number of rows.
你必须要保证UITableView和datasource保持一致的行数。
Thus, you must add a new BNRItem to the BNRItemStore before you insert the new row.
因此你要在UITableView添加新行前就把新的BNRItem添加到BNRItemStore。
-(IBAction)addNewItem:(id)sender{
BNRItem *newItem=[[BNRItemStore sharedStore] createItem];
NSInteger lastRow=[[[BNRItemStore sharedStore] allItems]indexOfObject:newItem];
NSIndexPath *indexpath=[NSIndexPath indexPathForRow:lastRow inSection:0];
[self.tableView insertRowsAtIndexPaths:@[indexpath] withRowAnimation:UITableViewRowAnimationTop];
}
1.3 Deleting Rows
1.3.1
Before the table view will delete a row, it sends its data source a message about the proposed deletion and waits for a confirmation message before pulling the trigger.
在table view删除一行之前,它发送给data source 一个信息关于删除的提议并等待确认的消息。
When deleting a cell, you must do two things: remove the row from the UITableView and remove the BNRItem associated with it from the BNRItemStore.
当删除一个cell时,你必须做两件事情:从UITableView中移除row(2)从BNRItemStore中把BNRItem移除。
- (void)removeItem:(BNRItem *)item;
- (void)removeItem:(BNRItem *)item
{
[self.privateItems removeObjectIdenticalTo:item];
}
1.3.2 removeObject VS removeObjectIdenticalTo
removeObject: goes to each object in the array and sends it the message isEqual:. A class can implement this method to return YES or NO based on its own determination. For example, two BNRItem objects could be considered equal if they had the same valueInDollars.
removeObject: 进入列逐个对象进行比较。发送isEqual消息。一个类能实现这个方法并返回yes or no 。
The method removeObjectIdenticalTo:, on the other hand, removes an object if and only if it is the exact same object as the one passed in this message.
removeObjectIdenticalTo当且仅当 它与给定的对象完全相同时才删除该对象。
1.3.3
you will implement tableView:commitEditingStyle:forRowAtIndexPath:, a method from the UITableViewDataSource protocol.
(This message is sent to the BNRItemsViewController. Keep in mind that while the BNRItemStore is the where the data is kept, the BNRItemsViewController is the table view's dataSource.)
这个消息被送给BNRItemsViewController。记住当BNRItemStore是data 保持的地方,BNRItemsViewController是table view 的datasource。
When tableView:commitEditingStyle:forRowAtIndexPath: is sent to the data source, two extra arguments are passed along with it. The first is the UITableViewCellEditingStyle, which, in this case, is UITableViewCellEditingStyleDelete. The other argument is the NSIndexPath of the row in the table.
当tableView:commitEditingStyle:forRowAtIndexPath被发送到data source 时,两个额外的参数也随着一起传递过来了。第一个是UITableViewCellEditingStyle,在这个情况下是UITableViewCellEditingStyleDelete,第二个是table 的行的NSIndexPath。
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
// If the table view is asking to commit a delete command... if (editingStyle == UITableViewCellEditingStyleDelete) {
NSArray *items = [[BNRItemStore sharedStore] allItems]; BNRItem *item = items[indexPath.row];
[[BNRItemStore sharedStore] removeItem:item];
// Also remove that row from the table view with an animation [tableView deleteRowsAtIndexPaths:@[indexPath]
} }
1.4 Moving Rows
Moving a row, however, does not require confirmation; the table view moves the row on its own authority and reports the move to its the data source by sending the message tableView:moveRowAtIndexPath:toIndexPath:.
移动一行,不需要确认。table view 在自己的领域内移动行,通过发送tableView:moveRowAtIndexPath:toIndexPath告诉这个移动给data source 。
But before you can implement the data source method, you need to give the BNRItemStore a method to change the order of items in its allItems array.
但在实现datasource方法之前,你应该给BNRItemStore一个方法让他该百年items在allItems 的顺序。
In BNRItemStore.h, declare this method.
- (void)moveItemAtIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex;
In BNRItemStore.m, implement moveItemAtIndex:toIndex:.
- (void)moveItemAtIndex:(NSUInteger)fromIndex
toIndex:(NSUInteger)toIndex
{
if (fromIndex == toIndex) {
return; }
// Get pointer to object being moved so you can re-insert it BNRItem *item = self.privateItems[fromIndex];
// Remove item from array
[self.privateItems removeObjectAtIndex:fromIndex];
// Insert item in array at new location
[self.privateItems insertObject:item atIndex:toIndex]; }
In BNRItemsViewController.m, implement tableView:moveRowAtIndexPath:toIndexPath: to update the store.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toIndexPath:(NSIndexPath *)destinationIndexPath {
[[BNRItemStore sharedStore] moveItemAtIndex:sourceIndexPath.row toIndex:destinationIndexPath.row];
}
The UITableView can ask its data source at runtime whether it implements tableView:moveRowAtIndexPath:toIndexPath:. If it does, the table view says, "Good, you can handle moving rows. I'll add the re-ordering controls." If not, it says, "If you aren't implementing that method, then I won't put controls there."
UITableView 将询问它的data source 是佛实现了tableView:moveRowAtIndexPath:toIndexPath。如果实现了,那么tableview 将会说"好啊,你能处理这个移动的行,我将添加重新排序控制",如果没有,"你没有实现这个方法,我不能实现这个控制。"