동적 높이가있는 행이있는보기 기반 NSTableView
보기 기반의 응용 프로그램이 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
RowResizableViews 를 보셨습니까 ? 그것은 꽤 오래되었고 나는 그것을 테스트하지 않았지만 그럼에도 불구하고 작동 할 수 있습니다.
이 문제를 해결하기 위해 수행 한 작업은 다음과 같습니다.
Source: Look into XCode documentation, under "row height nstableview". You'll find a sample source code named "TableViewVariableRowHeights/TableViewVariableRowHeightsAppDelegate.m"
(Note: I'm looking at column 1 in table view, you'll have to tweak to look elsewhere)
in Delegate.h
IBOutlet NSTableView *ideaTableView;
in Delegate.m
table view delegates control of row height
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row {
// Grab the fully prepared cell with our content filled in. Note that in IB the cell's Layout is set to Wraps.
NSCell *cell = [ideaTableView preparedCellAtColumn:1 row:row];
// See how tall it naturally would want to be if given a restricted with, but unbound height
CGFloat theWidth = [[[ideaTableView tableColumns] objectAtIndex:1] width];
NSRect constrainedBounds = NSMakeRect(0, 0, theWidth, CGFLOAT_MAX);
NSSize naturalSize = [cell cellSizeForBounds:constrainedBounds];
// compute and return row height
CGFloat result;
// Make sure we have a minimum height -- use the table's set height as the minimum.
if (naturalSize.height > [ideaTableView rowHeight]) {
result = naturalSize.height;
} else {
result = [ideaTableView rowHeight];
}
return result;
}
you also need this to effect the new row height (delegated method)
- (void)controlTextDidEndEditing:(NSNotification *)aNotification
{
[ideaTableView reloadData];
}
I hope this helps.
Final note: this does not support changing column width.
This sounds a lot like something I had to do previously. I wish I could tell you that I came up with a simple, elegant solution but, alas, I did not. Not for lack of trying though. As you have already noticed the need of UITableView to know the height prior to the cells being built really make it all seem quite circular.
My best solution was to push logic to the cell, because at least I could isolate what class needed to understand how the cells were laid out. A method like
+ (CGFloat) heightForStory:(Story*) story
would be able to determine how tall the cell had to be. Of course that involved measuring text, etc. In some cases I devised ways to cache information gained during this method that could then be used when the cell was created. That was the best I came up with. It is an infuriating problem though as it seems there should be a better answer.
Here is a solution based of JanApotheker's answer, modified as cellView.fittingSize.height
was not returning the correct height for me. In my case I am using the standard NSTableCellView
, an NSAttributedString
for the cell's textField text, and a single column table with constraints for the cell's textField set in IB.
In my view controller, I declare:
var tableViewCellForSizing: NSTableCellView?
viewDidLoad ()에서 :
tableViewCellForSizing = tableView.make(withIdentifier: "My Identifier", owner: self) as? NSTableCellView
마지막으로 tableView 델리게이트 메서드의 경우 :
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
guard let tableCellView = tableViewCellForSizing else { return minimumCellHeight }
tableCellView.textField?.attributedStringValue = attributedString[row]
if let height = tableCellView.textField?.fittingSize.height, height > 0 {
return height
}
return minimumCellHeight
}
mimimumCellHeight
백업을 위해 30으로 설정된 상수이지만 실제로 사용되지는 않습니다. attributedStrings
내 모델 배열입니다 NSAttributedString
.
이것은 내 필요에 완벽하게 작동합니다. 이 성가신 문제에 대한 올바른 방향을 알려주는 모든 이전 답변에 감사드립니다.
'Nice programing' 카테고리의 다른 글
JSON 문자열을 이스케이프하는 방법? (0) | 2020.10.04 |
---|---|
코드의 레이아웃 방향 (0) | 2020.10.04 |
큰 파일을 자르는 Unix 쉘 스크립트 (0) | 2020.10.04 |
Rails의 보호 및 비공개 메서드 (0) | 2020.10.04 |
"모두 제외"jQuery 선택기 (0) | 2020.10.04 |