뷰 기반이있는 응용 프로그램이 NSTableView
있습니다. 이 테이블 뷰 NSTextField
에는 단어 줄 바꿈이 활성화 된 다중 행으로 구성된 콘텐츠가있는 셀이있는 행이 있습니다 . 의 텍스트 내용에 NSTextField
따라 셀을 표시하는 데 필요한 행의 크기가 달라집니다.
높이를 반환 하는 NSTableViewDelegate
방법을 구현할 수 있다는 것을 알고 tableView:heightOfRow:
있지만 높이는 NSTextField
. 의 단어 줄 바꿈은의 NSTextField
너비에 따라 유사하게 NSTextField
…의 너비에 따라 결정됩니다 NSTableView
.
Soooo… 제 질문은… 이것에 대한 좋은 디자인 패턴은 무엇입니까? 내가 시도하는 모든 것이 복잡한 엉망인 것 같습니다. TableView는 셀을 배치하기 위해 셀의 높이에 대한 지식을 필요로하고 … NSTextField
단어 줄 바꿈을 결정하기 위해 레이아웃에 대한 지식이 필요하기 때문에 … 셀은 높이를 결정하기 위해 워드 랩에 대한 지식이 필요합니다. 그리고 그것은 나를 미치게 만듭니다.
제안?
중요한 경우 최종 결과도 편집 가능 NSTextFields
하며 그 안에있는 텍스트에 맞게 크기가 조정됩니다. 이미 뷰 레벨에서 작업하고 있지만 tableview는 아직 셀 높이를 조정하지 않습니다. 높이 문제가 해결되면- noteHeightOfRowsWithIndexesChanged
메서드를 사용 하여 높이가 변경되었음을 테이블 뷰에 알릴 것입니다.하지만 여전히 델리게이트에게 높이를 물어볼 것입니다. 따라서 제 질문입니다.
미리 감사드립니다!
답변
이것은 닭고기와 달걀 문제입니다. 테이블은 주어진 뷰가 놓일 위치를 결정하기 때문에 행 높이를 알아야합니다. 그러나 뷰가 이미 주변에 있기를 원하므로 행 높이를 알아내는 데 사용할 수 있습니다. 그래서, 어느 것이 먼저입니까?
대답은 NSTableCellView
뷰의 높이를 측정 하기 위해 추가 (또는 “셀 뷰”로 사용하는 뷰 )를 유지하는 것 입니다. 에서 tableView:heightOfRow:
위임 방법, ‘행’과 설정에 대한 액세스 모델 objectValue
에 NSTableCellView
. 그런 다음보기의 너비를 테이블의 너비로 설정하고 원하는대로 해당보기에 필요한 높이를 파악합니다. 그 값을 반환하십시오.
호출하지 마십시오 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