IT박스

NSFetchedResultsController-문자열의 첫 글자로 생성 된 섹션

itboxs 2020. 11. 1. 17:29
반응형

NSFetchedResultsController-문자열의 첫 글자로 생성 된 섹션


iPhone에서 핵심 데이터 학습. 섹션으로 테이블 뷰를 채우는 Core Data에 대한 예는 거의없는 것 같습니다. CoreDataBooks의 예는 섹션을 사용하지만 그들은 모델 내의 전체 문자열에서 발생하고 있습니다. 핵심 데이터 테이블을 성의 첫 글자 인 주소록에 따라 섹션으로 구성하고 싶습니다.

나는 들어가서 섹션 구분 역할을하기 위해 각 사람에 대해 다른 속성, 즉 단일 문자를 만들 수 있지만 이것은 복잡해 보입니다.

여기에 내가 시작하는 것이 있습니다 ... 속임수는 속이는 것 같습니다 sectionNameKeyPath.

- (NSFetchedResultsController *)fetchedResultsController {
//.........SOME STUFF DELETED
    // Edit the sort key as appropriate.
    NSSortDescriptor *orderDescriptor = [[NSSortDescriptor alloc] initWithKey:@"personName" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:orderDescriptor, nil];

    [fetchRequest setSortDescriptors:sortDescriptors];
    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = 
            [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest 
            managedObjectContext:managedObjectContext 
            sectionNameKeyPath:@"personName" cacheName:@"Root"];
//....
}

Dave DeLong의 접근 방식은 적어도 제 경우에는 몇 가지를 생략하는 한 좋습니다. 이것이 저에게 어떻게 작용하는지 다음과 같습니다.

  • "lastNameInitial"이라는 엔티티에 새로운 선택적 문자열 속성을 추가합니다.

    이 속성을 일시적으로 만듭니다. 이는 Core Data가 데이터 파일에 저장하는 것을 귀찮게하지 않음을 의미합니다. 이 속성은 필요할 때만 메모리에 존재합니다.

    이 엔티티에 대한 클래스 파일을 생성하십시오.

    이 속성에 대한 setter에 대해 걱정하지 마십시오. 이 게터 만들기 (이것은 마법의 절반, IMHO)


// THIS ATTRIBUTE GETTER GOES IN YOUR OBJECT MODEL
- (NSString *) committeeNameInitial {
    [self willAccessValueForKey:@"committeeNameInitial"];
    NSString * initial = [[self committeeName] substringToIndex:1];
    [self didAccessValueForKey:@"committeeNameInitial"];
    return initial;
}


// THIS GOES IN YOUR fetchedResultsController: METHOD
// Edit the sort key as appropriate.
NSSortDescriptor *nameInitialSortOrder = [[NSSortDescriptor alloc] 
        initWithKey:@"committeeName" ascending:YES];

[fetchRequest setSortDescriptors:[NSArray arrayWithObject:nameInitialSortOrder]];

NSFetchedResultsController *aFetchedResultsController = 
        [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest 
        managedObjectContext:managedObjectContext 
        sectionNameKeyPath:@"committeeNameInitial" cacheName:@"Root"];

이전 : 데이브의 초기 단계에 따라 유효하지 않은 인수 예외로 setPropertiesToFetch시 죽는 편지 생성 문제. 아래 코드와 디버깅 정보를 기록했습니다.

NSDictionary * entityProperties = [entity propertiesByName];
NSPropertyDescription * nameInitialProperty = [entityProperties objectForKey:@"committeeNameInitial"];
NSArray * tempPropertyArray = [NSArray arrayWithObject:nameInitialProperty];

//  NSARRAY * tempPropertyArray RETURNS:
//    <CFArray 0xf54090 [0x30307a00]>{type = immutable, count = 1, values = (
//    0 : (<NSAttributeDescription: 0xf2df80>), 
//    name committeeNameInitial, isOptional 1, isTransient 1,
//    entity CommitteeObj, renamingIdentifier committeeNameInitial, 
//    validation predicates (), warnings (), versionHashModifier (null), 
//    attributeType 700 , attributeValueClassName NSString, defaultValue (null)
//    )}

//  NSInvalidArgumentException AT THIS LINE vvvv
[fetchRequest setPropertiesToFetch:tempPropertyArray];

//  *** Terminating app due to uncaught exception 'NSInvalidArgumentException',
//    reason: 'Invalid property (<NSAttributeDescription: 0xf2dfb0>), 
//    name committeeNameInitial, isOptional 1, isTransient 1, entity CommitteeObj, 
//    renamingIdentifier committeeNameInitial, 
//    validation predicates (), warnings (), 
//    versionHashModifier (null), 
//    attributeType 700 , attributeValueClassName NSString, 
//    defaultValue (null) passed to setPropertiesToFetch: (property is transient)'

[fetchRequest setReturnsDistinctResults:YES];

NSSortDescriptor * nameInitialSortOrder = [[[NSSortDescriptor alloc]
    initWithKey:@"committeeNameInitial" ascending:YES] autorelease];

[fetchRequest setSortDescriptors:[NSArray arrayWithObject:nameInitialSortOrder]];

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] 
    initWithFetchRequest:fetchRequest 
    managedObjectContext:managedObjectContext 
    sectionNameKeyPath:@"committeeNameInitial" cacheName:@"Root"];

나는 또 다른 옵션이 있다고 생각하는데, 이것은 NSString의 카테고리를 사용합니다.

@implementation NSString (FetchedGroupByString)
- (NSString *)stringGroupByFirstInitial {
    if (!self.length || self.length == 1)
        return self;
    return [self substringToIndex:1];
}
@end

이제 조금 후에 FRC를 구성합니다.

- (NSFetchedResultsController *)newFRC {
    NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:awesomeRequest
            managedObjectContext:coolManagedObjectContext
            sectionNameKeyPath:@"lastName.stringGroupByFirstInitial"
            cacheName:@"CoolCat"];
    return frc;
}

이것이 제가 가장 좋아하는 접근 방식입니다. 훨씬 깨끗하고 구현하기 쉽습니다. 또한이를 지원하기 위해 개체 모델 클래스를 변경할 필요가 없습니다. 즉, 섹션 이름이 NSString을 기반으로하는 속성을 가리킬 경우 모든 개체 모델에서 작동합니다.


여기 방법은 일에 그것을 얻을 :

  • "lastNameInitial"이라는 엔티티에 새로운 선택적 문자열 속성을 추가합니다.
  • 이 속성을 일시적으로 만듭니다. 이는 Core Data가 데이터 파일에 저장하는 것을 귀찮게하지 않음을 의미합니다. 이 속성은 필요할 때만 메모리에 존재합니다.
  • 이 엔티티에 대한 클래스 파일을 생성하십시오.
  • 이 속성에 대한 setter에 대해 걱정하지 마십시오. 이 게터 만들기 (이것은 마법의 절반, IMHO)

    -(NSString *) lastNameInitial { 
    [self willAccessValueForKey : @ "lastNameInitial"];
    NSString * initial = [[self lastName] substringToIndex : 1];
    [self didAccessValueForKey : @ "lastNameInitial"];
    이니셜 반환;
    }
  • 가져 오기 요청에서 다음과 같이이 PropertyDescription 만 요청합니다 (이것은 마법의 또 다른 1/4입니다).

    NSDictionary * entityProperties = [myEntityDescription propertiesByName]; 
    NSPropertyDescription * lastNameInitialProperty = [entityProperties objectForKey : @ "lastNameInitial"];
    [fetchRequest setPropertiesToFetch : [NSArray arrayWithObject : lastNameInitialProperty]];
  • 가져 오기 요청이 고유 한 결과 만 반환하는지 확인합니다 (마법의 마지막 분기입니다).

    [fetchRequest setReturnsDistinctResults : YES];
  • 이 편지로 결과를 주문하십시오.

    NSSortDescriptor * lastNameInitialSortOrder = [[[NSSortDescriptor alloc] initWithKey : @ "lastNameInitial"오름차순 : YES] autorelease]; 
    [fetchRequest setSortDescriptors : [NSArray arrayWithObject : lastNameInitialSortOrder]];
  • 요청을 실행하고 그것이 무엇을 제공하는지 확인하십시오.

이것이 어떻게 작동하는지 이해한다면 NSManagedObjects의 배열을 반환 할 것이라고 생각합니다. 각 배열에는 lastNameInitial 속성 만 메모리에로드되고 고유 한 성 이니셜 집합이 있습니다.

행운을 빕니다. 그리고 이것이 어떻게 작동하는지 다시보고하십시오. 나는 이것을 내 머리 꼭대기에서 만들었고 이것이 작동하는지 알고 싶습니다. =)


위의 Greg Combs 답변을 좋아합니다. 문자열을 대문자로 변환하여 "Smith"및 "smith"와 같은 문자열이 동일한 섹션에 나타날 수 있도록 약간 수정했습니다.

- (NSString *)stringGroupByFirstInitial {
    NSString *temp = [self uppercaseString];
    if (!temp.length || temp.length == 1)
        return self;
    return [temp substringToIndex:1];
}

이 문제는 항상 발생합니다. 내가 항상 돌아 오는 가장 좋은 해결책은 엔티티에 실제 첫 번째 초기 속성을 제공하는 것입니다. 실제 필드는 필드를 인덱싱으로 설정할 수 있으므로보다 효율적인 검색 및 정렬을 제공합니다. 데이터를 처음 가져 오거나 만들 때 첫 번째 이니셜을 가져 와서 두 번째 필드를 채우는 것은 너무 많은 작업이 아닌 것 같습니다. 어느 쪽이든 초기 구문 분석 코드를 작성해야하지만 엔티티 당 한 번만 수행 할 수 있으며 다시는 수행 할 수 없습니다. 단점은 엔티티 당 하나의 추가 문자 (및 인덱싱)를 저장하는 것 같습니다. 이는 중요하지 않을 수 있습니다.

One extra note. I shy away from modifying the generated entity code. Maybe i'm missing something, but the tools for generating CoreData entities do not respect any code i might have put in there. Either option i pick when generating the code removes any customizations i might have made. If i fill up my entities with clever little functions, then i need to add a bunch of properties to that entity, i can't regenerate it easily.


swift 3

first, create extension to NSString (because CoreData is using basically NSString)

extension NSString{
    func firstChar() -> String{
        if self.length == 0{
            return ""
        }
        return self.substring(to: 1)
    }
}

Then sort using firstChar keypath, in my case, lastname.firstChar

request.sortDescriptors = [
            NSSortDescriptor(key: "lastname.firstChar", ascending: true),
            NSSortDescriptor(key: "lastname", ascending: true),
            NSSortDescriptor(key: "firstname", ascending: true)
        ]

And Finally Use the firstChar keypath for sectionNameKeyPath

let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "lastname.firstChar", cacheName: "your_cache_name")

I think I have a better way to do this. Instead of using transient property, in view will appear. Recalculate the derived property of the NSManagedObject and save the context.After the changes you can just reload the table view.

Here is an example of calculating the number of edges of each vertex, then sort the vertexes by the number of the edges. In this example, Capsid is vertex, touch is edge.

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView endUpdates];
    [self.tableView reloadData];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Capsid"];
    NSError *error = nil;
    NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
    if (error) {
        NSLog(@"refresh error");
        abort();
    }
    for (Capsid *capsid in results) {
        unsigned long long sum = 0;
        for (Touch *touch in capsid.vs) {
            sum += touch.count.unsignedLongLongValue;
        }
        for (Touch *touch in capsid.us) {
            sum += touch.count.unsignedLongLongValue;
        }
        capsid.sum = [NSNumber numberWithUnsignedLongLong:sum];
    }
    if (![self.managedObjectContext save:&error]) {
        NSLog(@"save error");
        abort();
    }
}

- (NSFetchedResultsController *)fetchedResultsController
{
    if (__fetchedResultsController != nil) {
        return __fetchedResultsController;
    }

    // Set up the fetched results controller.
    // Create the fetch request for the entity.
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Capsid" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];

    // Edit the sort key as appropriate.
    //    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO];
//    NSSortDescriptor *sumSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"sum" ascending:NO];
//    NSArray *sortDescriptors = [NSArray arrayWithObjects:sumSortDescriptor, nil];
    [fetchRequest setReturnsDistinctResults:YES];
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"sum" ascending:NO];
    NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
    [fetchRequest setSortDescriptors:sortDescriptors];


    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        /*
         Replace this implementation with code to handle the error appropriately.

         abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
         */
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return __fetchedResultsController;
} 

참고URL : https://stackoverflow.com/questions/1112521/nsfetchedresultscontroller-with-sections-created-by-first-letter-of-a-string

반응형