0

UITableViewで簡単なチェックリストを作成しています。ナビゲーションバーに通常の編集ボタンを配置して、編集機能を追加しました。ボタンは編集モードをオンにします。編集モードは、各セルのアクセサリビューにカスタムチェックボックス(ボタンとして)を追加するまではうまく機能します。私はそれを行うためにこのコードを使用しています:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];

        // put the tasks into the cell
        [[cell textLabel] setText:[NSString stringWithFormat:@"%@", [[[CLTaskStore sharedStore] allTasks] objectAtIndex:[indexPath row]]]];

        // put the checkbox into the cell's accessory view
        UIButton *checkBox = [UIButton buttonWithType:UIButtonTypeCustom];
        [checkBox setImage:[UIImage imageNamed:@"checkbox.png"] forState:UIControlStateNormal];
        [checkBox setImage:[UIImage imageNamed:@"checkbox-checked.png"] forState:UIControlStateSelected];
        checkBox.frame = CGRectMake(0, 0, 30, 30);
        checkBox.userInteractionEnabled = YES;
        [checkBox addTarget:self action:@selector(didCheckTask:) forControlEvents:UIControlEventTouchDown];
        cell.accessoryView = checkBox;

        // put the index path in the button's tag
        checkBox.tag = [indexPath row];
    }
    return cell;
}

ご覧のとおり、ボタンのタグを使用して、indexPathをdidCheckTaskに渡します。メソッド:

- (void)didCheckTask:(UIButton *)button
{
    task = [[[CLTaskStore sharedStore] allTasks] objectAtIndex:button.tag];
    task.didComplete = YES;

    // toggle checkbox
    button.selected = !button.selected;

    [checkList reloadData];
}

チェックボックスと編集はすべて、表面上は正しく機能しているようです。ただし、編集モードに入り、tableViewの項目を削除してから、チェックボックスを使用しようとすると、大きな問題が発生します。たとえば、tableViewの最初の項目を削除してから、最後の項目のチェックボックスをオンにしようとすると、プログラムは次のようにクラッシュします。

2012-05-06 21:45:40.645 CheckList [16022:f803] *キャッチされなかった例外'NSRangeException'が原因でアプリを終了しています。理由:'* -[__ NSArrayM objectAtIndex:]:インデックス4が境界を超えています[0 .. 3] '

私はこのバグの原因を突き止めようとしてきましたが、運がありません。私は本当にいくつかの助けを使うことができました-私はココアに不慣れです。関連するコードは次のとおりです。

CLTaskFactory.h

#import <Foundation/Foundation.h>

@interface CLTaskFactory : NSObject
{
    NSString *taskName;
    BOOL didComplete;
}

@property NSString *taskName;

- (void)setDidComplete:(BOOL)dc;
- (BOOL)didComplete;

@end

CLTaskFactory.m

#import "CLTaskFactory.h"

@implementation CLTaskFactory

@synthesize taskName;

- (void)setDidComplete:(BOOL)dc
{
    didComplete = dc;
}

- (BOOL)didComplete
{
    return didComplete;
}

- (NSString *)description
{
    // override the description
    NSString *descriptionString = [[NSString alloc] initWithFormat:@"%@", taskName];
    return descriptionString;
}

@end

CLTaskStore.h

#import <Foundation/Foundation.h>

@class CLTaskFactory;

@interface CLTaskStore : NSObject
{
    NSMutableArray *allTasks;
}

+ (CLTaskStore *)sharedStore;

- (NSMutableArray *)allTasks;
- (void)addTask:(CLTaskFactory *)task;
- (void)removeTask:(CLTaskFactory *)task;
- (void)moveTaskAtIndex:(int)from toIndex:(int)to;

@end

CLTaskStore.m

    #import "CLTaskStore.h"

    @implementation CLTaskStore

    + (id)allocWithZone:(NSZone *)zone
    {
        return [self sharedStore];
    }

    + (CLTaskStore *)sharedStore
    {
        static CLTaskStore *sharedStore = nil;
        if (!sharedStore) {
            sharedStore = [[super allocWithZone:nil] init];
        }
        return sharedStore;
    }

    - (id)init
    {
        self = [super init];
        if (self) {
            allTasks = [[NSMutableArray alloc] init];
        }
        return self;
    }

    - (NSMutableArray *)allTasks
    {
        return allTasks;
    }

    - (void)addTask:(CLTaskFactory *)task
    {
        [allTasks addObject:task];
    }

    - (void)removeTask:(CLTaskFactory *)task
    {
        [allTasks removeObjectIdenticalTo:task];

        NSInteger taskCount = [allTasks count];
        NSLog(@"Removed: %@, there are now %d remaining tasks, they are:", task, taskCount);
        for (int i = 0; i < taskCount; i++) {
            NSLog(@"%@", [[[CLTaskStore sharedStore] allTasks] objectAtIndex:i]);
        }
    }

    - (void)moveTaskAtIndex:(int)from toIndex:(int)to
    {
        if (from == to) {
            return;
        }

        CLTaskFactory *task = [allTasks objectAtIndex:from];
        [allTasks removeObjectAtIndex:from];
        [allTasks insertObject:task atIndex:to];
    }

    @end


CLChecklistViewController.h

    #import <Foundation/Foundation.h>

    @class CLTaskFactory;

    @interface CLCheckListViewController : UIViewController
    {
        CLTaskFactory *task;
    }

    - (void)didCheckTask:(UIButton *)button;

    @end

CLCheckListViewController.m

#import "CLCheckListViewController.h"
#import "CLTaskFactory.h"
#import "CLTaskStore.h"

@implementation CLCheckListViewController
{
    __weak IBOutlet UITableView *checkList;
}

- (id)init
{
    self = [super init];
    if (self) {
        // add five sample tasks
        CLTaskFactory *task1 = [[CLTaskFactory alloc] init];
        [task1 setTaskName:@"Task 1"];
        [task1 setDidComplete:NO];
        [[CLTaskStore sharedStore] addTask:task1];

        CLTaskFactory *task2 = [[CLTaskFactory alloc] init];
        [task2 setTaskName:@"Task 2"];
        [task2 setDidComplete:NO];
        [[CLTaskStore sharedStore] addTask:task2];

        CLTaskFactory *task3 = [[CLTaskFactory alloc] init];
        [task3 setTaskName:@"Task 3"];
        [task3 setDidComplete:NO];
        [[CLTaskStore sharedStore] addTask:task3];

        CLTaskFactory *task4 = [[CLTaskFactory alloc] init];
        [task4 setTaskName:@"Task 4"];
        [task4 setDidComplete:NO];
        [[CLTaskStore sharedStore] addTask:task4];

        CLTaskFactory *task5 = [[CLTaskFactory alloc] init];
        [task5 setTaskName:@"Task 5"];
        [task5 setDidComplete:NO];
        [[CLTaskStore sharedStore] addTask:task5];
    }
    return self;
}

- (void)viewDidLoad
{
    [[self navigationItem] setTitle:@"Checklist"];

    // create edit button
    [[self navigationItem] setLeftBarButtonItem:[self editButtonItem]];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [[[CLTaskStore sharedStore] allTasks] count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];

        // put the tasks into the cell
        [[cell textLabel] setText:[NSString stringWithFormat:@"%@", [[[CLTaskStore sharedStore] allTasks] objectAtIndex:[indexPath row]]]];

        // put the checkbox into the cell's accessory view
        UIButton *checkBox = [UIButton buttonWithType:UIButtonTypeCustom];
        [checkBox setImage:[UIImage imageNamed:@"checkbox.png"] forState:UIControlStateNormal];
        [checkBox setImage:[UIImage imageNamed:@"checkbox-checked.png"] forState:UIControlStateSelected];
        checkBox.frame = CGRectMake(0, 0, 30, 30);
        checkBox.userInteractionEnabled = YES;
        [checkBox addTarget:self action:@selector(didCheckTask:) forControlEvents:UIControlEventTouchDown];
        cell.accessoryView = checkBox;

        // put the index path in the button's tag
        checkBox.tag = [indexPath row];
    }
    return cell;
}

- (void)didCheckTask:(UIButton *)button
{
    task = [[[CLTaskStore sharedStore] allTasks] objectAtIndex:button.tag];
    task.didComplete = YES;

    // toggle checkbox
    button.selected = !button.selected;

    [checkList reloadData];
}

- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
    [super setEditing:editing animated:animated];

    // set editing mode
    if (editing) {
        self.navigationItem.title = @"Edit Checklist";
        [checkList setEditing:YES];
    } else {
        self.navigationItem.title = @"Checklist";
        [checkList setEditing:NO];
    }
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
                                            forRowAtIndexPath:(NSIndexPath *)indexPath
{
    // remove guest from store
    if (editingStyle == UITableViewCellEditingStyleDelete) {

        task = [[[CLTaskStore sharedStore] allTasks] objectAtIndex:[indexPath row]];
        [[CLTaskStore sharedStore] removeTask:task];

        // remove guest from table view
        [checkList deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }
}

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
    [[CLTaskStore sharedStore] moveTaskAtIndex:[sourceIndexPath row] toIndex:[destinationIndexPath row]];
}

@end

あなたの助けと専門知識を本当にありがとう!

編集:

洞察を得るために、NSLogをループする2つのメソッドを変更しました。まず、CLTaskStore:

- (void)removeTask:(CLTaskFactory *)task
{
    [allTasks removeObjectIdenticalTo:task];

    NSInteger taskCount = [allTasks count];
    NSLog(@"Removed: %@, there are now %d remaining tasks, they are:", task, taskCount);
    for (int i = 0; i < taskCount; i++) {
        NSLog(@"%@, status: %@", [[[CLTaskStore sharedStore] allTasks] objectAtIndex:i], [[[[CLTaskStore sharedStore] allTasks] objectAtIndex:i] didComplete]?@"YES":@"NO");
    }
}

次に、CLTaskListViewController:

- (void)didCheckTask:(UIButton *)button
{
    task = [[[CLTaskStore sharedStore] allTasks] objectAtIndex:button.tag];
    task.didComplete = YES;

    NSInteger taskCount = [[[CLTaskStore sharedStore] allTasks] count];
    for (int i = 0; i < taskCount; i++) {
        NSLog(@"%@, status: %@", [[[CLTaskStore sharedStore] allTasks] objectAtIndex:i], [[[[CLTaskStore sharedStore] allTasks] objectAtIndex:i] didComplete]?@"YES":@"NO");
    }

    // toggle checkbox
    button.selected = !button.selected;

    [checkList reloadData];
}

私は2つのことに気づきました。下から上に上向きに削除しても問題ありません。私は何でもチェックできます-すべてが機能します。ただし、最初の行を削除してから最後の行を確認すると、プログラムがクラッシュします。削除からのNSLogはクリーンで、正常に機能しています。

最初の行を削除して4番目の行を確認すると、CLTaskStoreレポートの行5のNSLogが確認されました。

これが問題です。削除後、2つは間違いなく順序が狂っています。

4

2 に答える 2

2

問題全体は、ボタンがどの行にあるかを示すためにタグを使用するという悪い考えに起因します。これは、データソースから行を削除しない場合は十分に悪いですが、削除する場合は、これが発生する可能性のある種類の問題です。 。

テーブルビューでタップされたアイテムの場所を使用し、テーブルビューから場所のインデックスパスを取得することは、はるかに堅牢で、編集可能なテーブルとマルチセクションテーブルで機能します。ここで私の答えのサンプルコードを参照してください。

そのようにすると、インデックスの再作成は必要ありません。

于 2012-05-09T05:47:29.640 に答える
0

tableViewの編集モードに入った後に削除ボタンが押された場合、対応するデータ項目をデータソースから削除する必要があります。あなたのコードはあなたがremoveTask:メソッドを持っていることを示していますが、データソースから対応するタスクエントリを削除するために実際にそのメソッドをどこで呼び出しているのかわかりません。これを行うのに適した場所は、ビューコントローラのtableview:commitEditingStyle:forRowAtIndexPath:メソッドです。

データソース内の対応するアイテムを削除しているため、コードをさらに詳しく調べると、チェックボックスタグの値が元の値のままであることがわかります。最後のアイテムの前にtableViewアイテムを削除してから、最後のアイテムをチェックしようとすると、didCheckTaskメソッドは元のindexPath行の値にアクセスしようとしますが、現在は存在せず、境界例外が発生します。最初の2つのセルを削除すると、最後の2つのtableViewアイテムの両方で例外が発生し、以下同様に続きます。

didCheckTaskメソッドでは機能しませんが、removeTask:メソッドでは、データソースからオブジェクトを削除した後、残りのオブジェクトをループして、各タグを対応する配列インデックスと等しく設定します。moveTaskAtIndex:toIndex:メソッドで、ユーザーがアイテムを並べ替えたために配列エントリを移動した後、同じことを行います。配列をループして、各タグを配列内のインデックスと等しく設定します。

于 2012-05-07T06:13:34.113 に答える