Mam aplikację z opartą na widoku NSTableView
. W tym widoku tabeli znajdują się wiersze zawierające komórki zawierające zawartość składającą się z wielu wierszy NSTextField
z włączonym zawijaniem wyrazów. W zależności od zawartości tekstowej NSTextField
komórki rozmiar wierszy potrzebnych do wyświetlenia komórki będzie różny.
Wiem, że mogę zaimplementować NSTableViewDelegate
metodę - tableView:heightOfRow:
aby zwrócić wysokość, ale wysokość zostanie określona na podstawie zawijania słów użytego na NSTextField
. Zawijanie wyrazów w NSTextField
podobny sposób opiera się na tym, jak szeroka NSTextField
jest…, która jest określana przez szerokość NSTableView
.
Soooo… Myślę, że moje pytanie brzmi… jaki jest dobry wzorzec projektowy do tego? Wygląda na to, że wszystko, czego próbuję, kończy się chaosem. Ponieważ TableView wymaga znajomości wysokości komórek, aby je ułożyć ... i NSTextField
znajomości jego układu, aby określić zawijanie wyrazów ... a komórka potrzebuje wiedzy o zawijaniu słów, aby określić jej wysokość ... to okrągły bałagan ... i doprowadza mnie to do szaleństwa .
Propozycje?
Jeśli ma to znaczenie, wynik końcowy będzie również dostępny do edycji, NSTextFields
który zmieni rozmiar, aby dopasować się do zawartego w nich tekstu. Mam już to działające na poziomie widoku, ale widok tabeli nie dostosowuje jeszcze wysokości komórek. Doszedłem do wniosku, że kiedy już rozwiążę kwestię wysokości, użyję noteHeightOfRowsWithIndexesChanged
metody - , aby poinformować widok stołu o zmianie wysokości… ale nadal będę pytać delegata o wysokość… stąd moja sprawa.
Z góry dziękuję!
źródło
Odpowiedzi:
To jest problem z kurczakiem i jajkiem. Tabela musi znać wysokość wiersza, ponieważ to określa, gdzie będzie leżeć dany widok. Ale chcesz, aby widok był już w pobliżu, więc możesz go użyć do obliczenia wysokości wiersza. Więc co jest pierwsze?
Odpowiedzią jest zachowanie dodatkowego
NSTableCellView
(lub innego widoku, którego używasz jako „widoku komórki”) tylko do pomiaru wysokości widoku. WtableView:heightOfRow:
metodzie delegata, dostęp model dla „rząd” i ustawićobjectValue
naNSTableCellView
. Następnie ustaw szerokość widoku na szerokość stołu i (jakkolwiek chcesz to zrobić) określ wymaganą wysokość dla tego widoku. Zwróć tę wartość.Nie wywołuj
noteHeightOfRowsWithIndexesChanged:
z metody delegatatableView:heightOfRow:
aniviewForTableColumn:row:
! To jest złe i spowoduje mega kłopoty.Aby dynamicznie aktualizować wysokość, to co powinieneś zrobić, to zareagować na zmianę tekstu (poprzez cel / akcję) i ponownie obliczyć obliczoną wysokość tego widoku. Teraz nie zmieniaj dynamicznie
NSTableCellView
wysokości (ani żadnego widoku, którego używasz jako „widoku komórki”). Stół musi kontrolować ramkę tego widoku, a jeśli spróbujesz go ustawić, będziesz walczył z widokiem tabeli. Zamiast tego, w celu / akcji dla pola tekstowego, w którym obliczałeś wysokość, wywołajnoteHeightOfRowsWithIndexesChanged:
, co pozwoli tabeli zmienić rozmiar tego pojedynczego wiersza. Zakładając, że masz ustawioną maskę autorezowania bezpośrednio na podwidokach (tj. PodwidokiNSTableCellView
), rozmiar rzeczy powinien się zmienić! Jeśli nie, najpierw popracuj nad maską zmiany rozmiaru podglądów podrzędnych, aby uzyskać prawidłowy efekt ze zmiennymi wysokościami wierszy.Nie zapominaj, że
noteHeightOfRowsWithIndexesChanged:
animacja jest domyślnie. Aby nie był animowany:[NSAnimationContext beginGrouping]; [[NSAnimationContext currentContext] setDuration:0]; [tableView noteHeightOfRowsWithIndexesChanged:indexSet]; [NSAnimationContext endGrouping];
PS: Odpowiadam bardziej na pytania publikowane na forach Apple dla deweloperów niż przepełnienie stosu.
PSS: Napisałem NSTableView w oparciu o widok
źródło
tableView:heightOfRow:
konfiguruję widok zapasowej komórki z wartościami wiersza (mam tylko jedną kolumnę) i zwracamcellView.fittingSize.height
.fittingSize
jest metodą NSView, która oblicza minimalny rozmiar widoku, który spełnia jego ograniczenia.To stało się o wiele łatwiejsze w macOS 10.13 z
.usesAutomaticRowHeights
. Szczegóły są tutaj: https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKit/#10_13 (w sekcji zatytułowanej „NSTableView Automatic Row Heights”).Zasadniczo po prostu wybierz swój
NSTableView
lubNSOutlineView
w edytorze scenorysów i wybierz tę opcję w Inspektorze rozmiaru:Następnie ustawisz rzeczy w swoim tak,
NSTableCellView
aby miały górne i dolne ograniczenia do komórki, a twoja komórka zmieni rozmiar, aby dopasować się automatycznie. Nie jest wymagany kod!Twoja aplikacja zignoruje wszelkie wysokości określone w
heightOfRow
(NSTableView
) iheightOfRowByItem
(NSOutlineView
). Możesz zobaczyć, jakie wysokości są obliczane dla wierszy automatycznego układu za pomocą tej metody:func outlineView(_ outlineView: NSOutlineView, didAdd rowView: NSTableRowView, forRow row: Int) { print(rowView.fittingSize.height) }
źródło
TableCellView
NIE można go edytować. Nie ma znaczenia jest zaznaczone w Attributes [Behavior: Editable] lub Bindings Inspector [Conditionally Set Editable: Yes]W oparciu o odpowiedź Corbina (przy okazji dzięki, rzucając trochę światła na to):
Swift 3, oparty na widoku NSTableView z automatycznym układem dla systemu macOS 10.11 (i nowszych)
Moja konfiguracja: mam układ,
NSTableCellView
który jest rozłożony przy użyciu automatycznego układu. Zawiera (oprócz innych elementów) multilinię,NSTextField
która może mieć do 2 wierszy. Dlatego wysokość całego widoku komórki zależy od wysokości tego pola tekstowego.Aktualizuję, aby widok tabeli zaktualizował wysokość dwukrotnie:
1) Gdy zmienia się rozmiar widoku tabeli:
func tableViewColumnDidResize(_ notification: Notification) { let allIndexes = IndexSet(integersIn: 0..<tableView.numberOfRows) tableView.noteHeightOfRows(withIndexesChanged: allIndexes) }
2) Gdy zmieni się obiekt modelu danych:
tableView.noteHeightOfRows(withIndexesChanged: changedIndexes)
Spowoduje to, że widok tabeli zapyta delegata o nową wysokość wiersza.
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 }
Najpierw musimy pobrać obiekt modelu dla wiersza (
entity
) i odpowiedni identyfikator widoku komórki. Następnie sprawdzamy, czy utworzyliśmy już widok dla tego identyfikatora. Aby to zrobić, musimy zachować listę z widokami komórek dla każdego identyfikatora:// We need to keep one cell view (per identifier) around fileprivate var savedTableCellViews = [String : NSTableCellView]()
Jeśli żaden nie zostanie zapisany, musimy utworzyć (i zapisać w pamięci podręcznej) nowy. Aktualizujemy widok komórki za pomocą naszego obiektu modelu i nakazujemy mu ponowne ułożenie wszystkiego w oparciu o aktualną szerokość widoku tabeli.
fittingSize
Wysokość może być następnie wykorzystany jako nowej wysokości.źródło
cellView.bounds.size.width = tableView.bounds.size.width
warto zresetować wysokość do małej liczby. Jeśli planujesz ponownie wykorzystać ten fikcyjny widok, ustawienie go na niską liczbę „zresetuje” rozmiar dopasowania.Dla każdego, kto chce więcej kodu, oto pełne rozwiązanie, którego użyłem. Dzięki Corbin Dunn za wskazanie mi właściwego kierunku.
Musiałem ustawić wysokość głównie w odniesieniu do tego, jak wysoko
NSTextView
w moimNSTableViewCell
.W mojej podklasie
NSViewController
tymczasowo tworzę nową komórkę, dzwoniącoutlineView: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; }
W moim,
IBAnnotationTableViewCell
który jest kontrolerem mojej komórki (podklasyNSTableCellView
), mam metodę konfiguracji-(void)setupWithPDFAnnotation:(PDFAnnotation*)annotation;
który ustawia wszystkie wyloty i ustawia tekst z moich adnotacji PDF. Teraz mogę „łatwo” obliczyć wysokość używając:
-(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 ; }
źródło
Szukałem rozwiązania od dłuższego czasu i wpadłem na takie, które w moim przypadku świetnie się sprawdza:
- (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; } }
źródło
Ponieważ używam niestandardowego
NSTableCellView
i mam dostęp doNSTextField
mojego rozwiązania było dodanie metodyNSTextField
.@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
źródło
Czy spojrzałeś na RowResizableViews ? Jest dość stary i nie testowałem go, ale mimo to może działać.
źródło
Oto, co zrobiłem, aby to naprawić:
Źródło: zajrzyj do dokumentacji XCode, w sekcji „wysokość wiersza nstableview”. Znajdziesz przykładowy kod źródłowy o nazwie „TableViewVariableRowHeights / TableViewVariableRowHeightsAppDelegate.m”
(Uwaga: patrzę na kolumnę 1 w widoku tabeli, musisz dostosować, aby szukać gdzie indziej)
w Delegate.h
IBOutlet NSTableView *ideaTableView;
w Delegate. m
widok tabeli deleguje kontrolę wysokości wiersza
- (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; }
potrzebujesz tego również, aby wpłynąć na nową wysokość wiersza (metoda delegowana)
- (void)controlTextDidEndEditing:(NSNotification *)aNotification { [ideaTableView reloadData]; }
Mam nadzieję, że to pomoże.
Ostatnia uwaga: to nie obsługuje zmiany szerokości kolumny.
źródło
-[NSTableView preparedCellAtColumn:row]
, które według dokumentów „są dostępne tylko dla widoków tabel opartych na NSCell”. Użycie tej metody w tabeli opartej na widoku powoduje wygenerowanie tego dziennika w czasie wykonywania: „Błąd NSTableView oparty na widoku: wywołano preparowanyCellAtColumn: wiersz:. Proszę zarejestrować błąd z zapisem wstecznym z tego dziennika lub przestać używać tej metody”.Oto rozwiązanie oparte na odpowiedzi JanApothekera, zmodyfikowane, ponieważ
cellView.fittingSize.height
nie zwracało mi prawidłowej wysokości. W moim przypadku używam standardNSTableCellView
,NSAttributedString
dla komórki textField tekst, a pojedynczą tabelę kolumnowej z ograniczeniami dla komórki zestaw textField w IB.Moim zdaniem kontroler oświadczam:
var tableViewCellForSizing: NSTableCellView?
W widokuDidLoad ():
tableViewCellForSizing = tableView.make(withIdentifier: "My Identifier", owner: self) as? NSTableCellView
Na koniec dla metody delegata 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
jest stałą ustawioną na 30 do tworzenia kopii zapasowych, ale nigdy nie jest używana.attributedStrings
to moja tablica modeliNSAttributedString
.Działa to idealnie na moje potrzeby. Dzięki za wszystkie poprzednie odpowiedzi, które wskazały mi właściwy kierunek dla tego brzydkiego problemu.
źródło
To brzmi jak coś, co musiałem zrobić wcześniej. Chciałbym móc powiedzieć, że wymyśliłem proste, eleganckie rozwiązanie, ale niestety nie. Nie z powodu braku prób. Jak już zauważyłeś, potrzeba, aby UITableView znać wysokość przed zbudowaniem komórek, naprawdę sprawia, że wszystko wydaje się dość okrągłe.
Moim najlepszym rozwiązaniem było przeniesienie logiki do komórki, ponieważ przynajmniej mogłem wyodrębnić klasę potrzebną do zrozumienia, jak układają się komórki. Metoda taka jak
+ (CGFloat) heightForStory:(Story*) story
będzie w stanie określić, jak wysoka musi być komórka. Oczywiście wymagało to pomiaru tekstu itp. W niektórych przypadkach wymyśliłem sposoby buforowania informacji uzyskanych za pomocą tej metody, które można było następnie wykorzystać podczas tworzenia komórki. To było najlepsze, co wymyśliłem. Jest to irytujący problem, chociaż wydaje się, że powinna istnieć lepsza odpowiedź.
źródło