[objective-c] 동적 높이가있는 행이있는보기 기반 NSTableView

뷰 기반이있는 응용 프로그램이 NSTableView있습니다. 이 테이블 뷰 NSTextField에는 단어 줄 바꿈이 활성화 된 다중 행으로 구성된 콘텐츠가있는 셀이있는 행이 있습니다 . 의 텍스트 내용에 NSTextField따라 셀을 표시하는 데 필요한 행의 크기가 달라집니다.

높이를 반환 하는 NSTableViewDelegate방법을 구현할 수 있다는 것을 알고 tableView:heightOfRow:있지만 높이는 NSTextField. 의 단어 줄 바꿈은의 NSTextField너비에 따라 유사하게 NSTextField…의 너비에 따라 결정됩니다 NSTableView.

Soooo… 제 질문은… 이것에 대한 좋은 디자인 패턴은 무엇입니까? 내가 시도하는 모든 것이 복잡한 엉망인 것 같습니다. TableView는 셀을 배치하기 위해 셀의 높이에 대한 지식을 필요로하고 … NSTextField단어 줄 바꿈을 결정하기 위해 레이아웃에 대한 지식이 필요하기 때문에 … 셀은 높이를 결정하기 위해 워드 랩에 대한 지식이 필요합니다. 그리고 그것은 나를 미치게 만듭니다.

제안?

중요한 경우 최종 결과도 편집 가능 NSTextFields하며 그 안에있는 텍스트에 맞게 크기가 조정됩니다. 이미 뷰 레벨에서 작업하고 있지만 tableview는 아직 셀 높이를 조정하지 않습니다. 높이 문제가 해결되면- noteHeightOfRowsWithIndexesChanged메서드를 사용 하여 높이가 변경되었음을 테이블 뷰에 알릴 것입니다.하지만 여전히 델리게이트에게 높이를 물어볼 것입니다. 따라서 제 질문입니다.

미리 감사드립니다!



답변

이것은 닭고기와 달걀 문제입니다. 테이블은 주어진 뷰가 놓일 위치를 결정하기 때문에 행 높이를 알아야합니다. 그러나 뷰가 이미 주변에 있기를 원하므로 행 높이를 알아내는 데 사용할 수 있습니다. 그래서, 어느 것이 먼저입니까?

대답은 NSTableCellView뷰의 높이를 측정 하기 위해 추가 (또는 “셀 뷰”로 사용하는 뷰 )를 유지하는 것 입니다. 에서 tableView:heightOfRow:위임 방법, ‘행’과 설정에 대한 액세스 모델 objectValueNSTableCellView. 그런 다음보기의 너비를 테이블의 너비로 설정하고 원하는대로 해당보기에 필요한 높이를 파악합니다. 그 값을 반환하십시오.

호출하지 마십시오 noteHeightOfRowsWithIndexesChanged:대의원 방법에서 tableView:heightOfRow:viewForTableColumn:row:! 그것은 나쁘고 큰 문제를 일으킬 것입니다.

높이를 동적으로 업데이트하려면 (타겟 / 액션을 통해) 텍스트 변경에 응답하고 해당 뷰의 계산 된 높이를 다시 계산해야합니다. 이제 NSTableCellView의 높이 (또는 “셀보기”로 사용중인보기)를 동적으로 변경하지 마십시오 . 테이블 해당 뷰의 프레임을 제어 해야 하며 설정하려고하면 테이블 뷰와 싸우게됩니다. 대신 높이를 계산 한 텍스트 필드에 대한 대상 / 작업에서을 호출 noteHeightOfRowsWithIndexesChanged:하면 테이블이 해당 개별 행의 크기를 조정할 수 있습니다. 하위 뷰 (예 :의 하위 뷰)에 자동 크기 조정 마스크 설정이 있다고 가정하면 NSTableCellView크기가 잘 조정되어야합니다! 그렇지 않은 경우 먼저 하위 뷰의 크기 조정 마스크에서 작업하여 가변 행 높이로 문제를 해결하십시오.

noteHeightOfRowsWithIndexesChanged:기본적으로 애니메이션 된다는 것을 잊지 마십시오 . 애니메이션하지 않으려면 :

[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0];
[tableView noteHeightOfRowsWithIndexesChanged:indexSet];
[NSAnimationContext endGrouping];

추신 : 저는 스택 오버플로보다 Apple Dev Forums에 게시 된 질문에 더 많이 답변합니다.

PSS : NSTableView 기반 뷰를 작성했습니다.


답변

이에 훨씬 쉬워졌습니다 맥 OS 10.13.usesAutomaticRowHeights. 자세한 내용은 https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKit/#10_13(“NSTableView 자동 행 높이 “섹션)에 있습니다.

기본적으로 NSTableView또는 NSOutlineView스토리 보드 편집기에서 선택하고 크기 검사기에서이 옵션을 선택합니다.

여기에 이미지 설명 입력

그런 다음 NSTableCellView셀에 대한 상단 및 하단 제약 조건을 갖도록 항목 을 설정하면 셀 크기가 자동으로 조정됩니다. 코드가 필요하지 않습니다!

앱은 heightOfRow( NSTableView) 및 heightOfRowByItem( NSOutlineView)에 지정된 높이를 무시합니다 . 이 방법으로 자동 레이아웃 행에 대해 계산되는 높이를 확인할 수 있습니다.

func outlineView(_ outlineView: NSOutlineView, didAdd rowView: NSTableRowView, forRow row: Int) {
  print(rowView.fittingSize.height)
}


답변

Corbin의 답변을 기반으로합니다 (btw 감사합니다).

macOS 10.11 (이상) 용 자동 레이아웃이있는 Swift 3,보기 기반 NSTableView

내 설정 : NSTableCellView자동 레이아웃을 사용하여 레이아웃이 있습니다. 여기에는 다른 요소 외에 NSTextField최대 2 개의 행을 가질 수 있는 여러 줄 이 포함됩니다. 따라서 전체 셀보기의 높이는이 텍스트 필드의 높이에 따라 달라집니다.

두 가지 경우에 높이를 업데이트하도록 테이블 뷰를 업데이트합니다.

1) 테이블보기 크기가 조정되는 경우 :

func tableViewColumnDidResize(_ notification: Notification) {
        let allIndexes = IndexSet(integersIn: 0..<tableView.numberOfRows)
        tableView.noteHeightOfRows(withIndexesChanged: allIndexes)
}

2) 데이터 모델 개체가 변경되는 경우 :

tableView.noteHeightOfRows(withIndexesChanged: changedIndexes)

이렇게하면 테이블 뷰가 대리자에게 새 행 높이를 요청합니다.

func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {

    // Get data object for this row
    let entity = dataChangesController.entities[row]

    // Receive the appropriate cell identifier for your model object
    let cellViewIdentifier = tableCellViewIdentifier(for: entity)

    // We use an implicitly unwrapped optional to crash if we can't create a new cell view
    var cellView: NSTableCellView!

    // Check if we already have a cell view for this identifier
    if let savedView = savedTableCellViews[cellViewIdentifier] {
        cellView = savedView
    }
    // If not, create and cache one
    else if let view = tableView.make(withIdentifier: cellViewIdentifier, owner: nil) as? NSTableCellView {
        savedTableCellViews[cellViewIdentifier] = view
        cellView = view
    }

    // Set data object
    if let entityHandler = cellView as? DataEntityHandler {
        entityHandler.update(with: entity)
    }

    // Layout
    cellView.bounds.size.width = tableView.bounds.size.width
    cellView.needsLayout = true
    cellView.layoutSubtreeIfNeeded()

    let height = cellView.fittingSize.height

    // Make sure we return at least the table view height
    return height > tableView.rowHeight ? height : tableView.rowHeight
}

먼저, 행 ( entity)에 대한 모델 객체 와 적절한 셀 뷰 식별자를 가져와야합니다. 그런 다음이 식별자에 대한 뷰를 이미 생성했는지 확인합니다. 이를 위해 각 식별자에 대한 셀 뷰가있는 목록을 유지해야합니다.

// We need to keep one cell view (per identifier) around
fileprivate var savedTableCellViews = [String : NSTableCellView]()

저장되지 않은 경우 새 항목을 만들고 캐시해야합니다. 모델 객체로 셀 뷰를 업데이트하고 현재 테이블 뷰 너비를 기반으로 모든 것을 다시 레이아웃하도록 지시합니다. fittingSize높이는 다음의 새로운 높이로 사용할 수 있습니다.


답변

더 많은 코드를 원하는 사람을 위해 내가 사용한 전체 솔루션은 다음과 같습니다. 저를 올바른 방향으로 안내 해준 corbin dunn에게 감사드립니다.

나는 주로 NSTextView나의 높이와 관련하여 높이를 설정해야했습니다 NSTableViewCell.

내 하위 클래스에서 NSViewController임시로 다음을 호출하여 새 셀을 만듭니다.outlineView:viewForTableColumn:item:

- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
{
    NSTableColumn *tabCol = [[outlineView tableColumns] objectAtIndex:0];
    IBAnnotationTableViewCell *tableViewCell = (IBAnnotationTableViewCell*)[self outlineView:outlineView viewForTableColumn:tabCol item:item];
    float height = [tableViewCell getHeightOfCell];
    return height;
}

- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    IBAnnotationTableViewCell *tableViewCell = [outlineView makeViewWithIdentifier:@"AnnotationTableViewCell" owner:self];
    PDFAnnotation *annotation = (PDFAnnotation *)item;
    [tableViewCell setupWithPDFAnnotation:annotation];
    return tableViewCell;
}

내에서 IBAnnotationTableViewCell하는 내 셀 컨트롤러 (의 서브 클래스 NSTableCellView) 내가 설정 방법을

-(void)setupWithPDFAnnotation:(PDFAnnotation*)annotation;

모든 콘센트를 설정하고 내 PDFAnnotations의 텍스트를 설정합니다. 이제 다음을 사용하여 높이를 “쉽게”계산할 수 있습니다.

-(float)getHeightOfCell
{
    return [self getHeightOfContentTextView] + 60;
}

-(float)getHeightOfContentTextView
{
    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[self.contentTextView font],NSFontAttributeName,nil];
    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:[self.contentTextView string] attributes:attributes];
    CGFloat height = [self heightForWidth: [self.contentTextView frame].size.width forString:attributedString];
    return height;
}

.

- (NSSize)sizeForWidth:(float)width height:(float)height forString:(NSAttributedString*)string
{
    NSInteger gNSStringGeometricsTypesetterBehavior = NSTypesetterLatestBehavior ;
    NSSize answer = NSZeroSize ;
    if ([string length] > 0) {
        // Checking for empty string is necessary since Layout Manager will give the nominal
        // height of one line if length is 0.  Our API specifies 0.0 for an empty string.
        NSSize size = NSMakeSize(width, height) ;
        NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:size] ;
        NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:string] ;
        NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init] ;
        [layoutManager addTextContainer:textContainer] ;
        [textStorage addLayoutManager:layoutManager] ;
        [layoutManager setHyphenationFactor:0.0] ;
        if (gNSStringGeometricsTypesetterBehavior != NSTypesetterLatestBehavior) {
            [layoutManager setTypesetterBehavior:gNSStringGeometricsTypesetterBehavior] ;
        }
        // NSLayoutManager is lazy, so we need the following kludge to force layout:
        [layoutManager glyphRangeForTextContainer:textContainer] ;

        answer = [layoutManager usedRectForTextContainer:textContainer].size ;

        // Adjust if there is extra height for the cursor
        NSSize extraLineSize = [layoutManager extraLineFragmentRect].size ;
        if (extraLineSize.height > 0) {
            answer.height -= extraLineSize.height ;
        }

        // In case we changed it above, set typesetterBehavior back
        // to the default value.
        gNSStringGeometricsTypesetterBehavior = NSTypesetterLatestBehavior ;
    }

    return answer ;
}

.

- (float)heightForWidth:(float)width forString:(NSAttributedString*)string
{
    return [self sizeForWidth:width height:FLT_MAX forString:string].height ;
}


답변

나는 꽤 오랫동안 해결책을 찾고 있었고 내 경우에는 잘 작동하는 다음과 같은 해결책을 찾았습니다.

- (double)tableView:(NSTableView *)tableView heightOfRow:(long)row
{
    if (tableView == self.tableViewTodo)
    {
        CKRecord *record = [self.arrayTodoItemsFiltered objectAtIndex:row];
        NSString *text = record[@"title"];

        double someWidth = self.tableViewTodo.frame.size.width;
        NSFont *font = [NSFont fontWithName:@"Palatino-Roman" size:13.0];
        NSDictionary *attrsDictionary =
        [NSDictionary dictionaryWithObject:font
                                    forKey:NSFontAttributeName];
        NSAttributedString *attrString =
        [[NSAttributedString alloc] initWithString:text
                                        attributes:attrsDictionary];

        NSRect frame = NSMakeRect(0, 0, someWidth, MAXFLOAT);
        NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
        [[tv textStorage] setAttributedString:attrString];
        [tv setHorizontallyResizable:NO];
        [tv sizeToFit];

        double height = tv.frame.size.height + 20;

        return height;
    }

    else
    {
        return 18;
    }
}


답변

사용자 지정을 사용 NSTableCellView하고 NSTextField내 솔루션에 대한 액세스 권한이 있기 때문에 NSTextField.

@implementation NSTextField (IDDAppKit)

- (CGFloat)heightForWidth:(CGFloat)width {
    CGSize size = NSMakeSize(width, 0);
    NSFont*  font = self.font;
    NSDictionary*  attributesDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
    NSRect bounds = [self.stringValue boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:attributesDictionary];

    return bounds.size.height;
}

@end


답변

RowResizableViews보셨습니까 ? 그것은 꽤 오래되었고 나는 그것을 테스트하지 않았지만 그럼에도 불구하고 작동 할 수 있습니다.