objective c - गतिशील ऊंचाई वाली पंक्तियों के साथ व्यू-आधारित एनएसटीबल व्यू




objective-c macos (7)

किसी और कोड को चाहते हैं, यहां मेरा पूरा समाधान है। सही दिशा में मुझे इंगित करने के लिए धन्यवाद कॉर्बिन डन।

मुझे अपने NSTextView में NSTextView कितना उच्च था, इस संबंध में ऊंचाई को सेट करने की आवश्यकता थी।

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;

जो सभी आउटलेट सेट करता है और मेरे पीडीएफएनोटेशन से टेक्स्ट सेट करता है। अब मैं ऊंचाई का उपयोग करके "आसानी से" कलकत्ता कर सकता हूं:

-(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 ;
}

मेरे पास एक दृश्य-आधारित NSTableView व्यू के साथ एक एप्लिकेशन है। इस तालिका के दृश्य के अंदर, मेरे पास ऐसी पंक्तियां हैं जिनमें वे कक्ष हैं जिनमें वर्ड-रैप सक्षम के साथ बहु-पंक्ति NSTextField शामिल सामग्री है। एनएसटीक्स्टफिल्ड की पाठ्य सामग्री के आधार पर, सेल को प्रदर्शित करने के लिए आवश्यक पंक्तियों का आकार अलग-अलग होगा।

मुझे पता है कि मैं NSTableViewDelegate विधि को कार्यान्वित कर सकता हूं - tableView:heightOfRow: ऊंचाई को वापस करने के लिए, लेकिन ऊंचाई tableView:heightOfRow: पर उपयोग किए गए शब्द रैपिंग के आधार पर निर्धारित की जाएगी। NSTextField का शब्द रैपिंग इसी तरह आधारित है कि NSTextField कितनी व्यापक है ... जिसे NSTableView की चौड़ाई द्वारा निर्धारित किया NSTableView

Soooo ... मुझे लगता है कि मेरा सवाल है ... इसके लिए एक अच्छा डिजाइन पैटर्न क्या है? ऐसा लगता है कि मैं हवाओं को एक गड़बड़ कर रहा हूं। चूंकि टेबलव्यू को कोशिकाओं की ऊंचाई को ज्ञान देने के लिए ज्ञान की आवश्यकता होती है ... और NSTextField को शब्द रैप निर्धारित करने के लिए इसके लेआउट के ज्ञान की आवश्यकता होती है ... और सेल को इसकी ऊंचाई निर्धारित करने के लिए शब्द रैप के ज्ञान की आवश्यकता होती है ... यह एक गोलाकार गड़बड़ है ... और यह मुझे पागल कर रहा है

सुझाव?

यदि यह महत्वपूर्ण है, तो अंतिम परिणाम में संपादन योग्य NSTextFields भी होंगे जो उनके भीतर पाठ में समायोजित करने के लिए आकार बदलेंगे। मेरे पास पहले से ही दृश्य स्तर पर काम कर रहा है, लेकिन तालिकादृश्य अभी तक कोशिकाओं की ऊंचाइयों को समायोजित नहीं करता है। मुझे लगता है कि एक बार जब मैं ऊंचाई मुद्दा तैयार कर noteHeightOfRowsWithIndexesChanged हूं, तो मैं तालिका का सूचित करने के लिए noteHeightOfRowsWithIndexesChanged विधि का उपयोग noteHeightOfRowsWithIndexesChanged , ऊंचाई को बदलता है ... लेकिन फिर भी यह प्रतिनिधि के लिए ऊंचाई से पूछने जा रहा है ... इसलिए, मेरी कन्डीरी।

अग्रिम में धन्यवाद!


कॉर्बिन के उत्तर के आधार पर (बीटीडब्ल्यू धन्यवाद इस पर कुछ प्रकाश डालना):

स्विफ्ट 3, मैकोज़ 10.11 (और ऊपर) के लिए ऑटो-लेआउट के साथ व्यू-आधारित एनएसटीबल व्यू

मेरा सेटअप: मेरे पास एक 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 ऊंचाई को तब नई ऊंचाई के रूप में उपयोग किया जा सकता है।


चूंकि मैं कस्टम NSTableCellView उपयोग करता NSTableCellView और मेरे पास NSTableCellView तक पहुंच है, मेरा समाधान NSTableCellView पर एक विधि जोड़ना था।

@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

मैं काफी समय के लिए एक समाधान की तलाश में था और निम्नलिखित के साथ आया, जो मेरे मामले में महान काम करता है:

- (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;
    }
}

यह ऐसा कुछ लगता है जो मुझे पहले करना था। काश मैं आपको बता सकता हूं कि मैं एक साधारण, सुरुचिपूर्ण समाधान के साथ आया था, लेकिन, हां, मैंने नहीं किया। हालांकि कोशिश करने की कमी के लिए नहीं। जैसा कि आपने पहले से ही कोशिकाओं के निर्माण से पहले ऊंचाई जानने के लिए UITableView की आवश्यकता को देखा है, वास्तव में यह सब काफी परिपत्र प्रतीत होता है।

मेरा सबसे अच्छा समाधान सेल को तर्क को धक्का देना था, क्योंकि कम से कम मैं अलग-अलग वर्ग को अलग कर सकता था कि कोशिकाओं को कैसे रखा गया था। एक विधि की तरह

+ (CGFloat) heightForStory:(Story*) story

यह निर्धारित करने में सक्षम होगा कि सेल कितना लंबा होना था। बेशक, पाठ को मापने में शामिल थे, आदि। कुछ मामलों में मैंने इस विधि के दौरान प्राप्त जानकारी को कैश करने के तरीके तैयार किए जिन्हें तब बनाया जा सकता था जब सेल बनाया गया था। वह सबसे अच्छा था जिसके साथ मैं आया था। यह एक क्रूर समस्या है हालांकि ऐसा लगता है कि एक बेहतर जवाब होना चाहिए।


यह ठीक करने के लिए मैंने किया है:

स्रोत: "पंक्ति ऊंचाई nstableview" के तहत, एक्सकोड दस्तावेज़ में देखें। आपको "TableViewVariableRowHeights / TableViewVariableRowHeightsAppDelegate.m" नामक एक नमूना स्रोत कोड मिलेगा

(नोट: मैं तालिका दृश्य में कॉलम 1 देख रहा हूं, आपको कहीं और देखने के लिए ट्विक करना होगा)

Delegate.h में

IBOutlet NSTableView            *ideaTableView;

Delegate.m में

तालिका दृश्य प्रतिनिधियों पंक्ति ऊंचाई का नियंत्रण

    - (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;
}

आपको नई पंक्ति ऊंचाई (प्रतिनिधि विधि) को प्रभावित करने के लिए भी इसकी आवश्यकता है

- (void)controlTextDidEndEditing:(NSNotification *)aNotification
{
    [ideaTableView reloadData];
}

आशा है कि ये आपकी मदद करेगा।

अंतिम नोट: यह कॉलम चौड़ाई बदलने का समर्थन नहीं करता है।


मैकोज़ 10.13 में .usesAutomaticRowHeights साथ यह बहुत आसान हो गया। विवरण यहां हैं: https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKit/#10_13 ("एनएसटीबल व्यू स्वचालित पंक्ति ऊंचाइयों" शीर्षक वाले अनुभाग में)।

मूल रूप से आप स्टोरीबोर्ड संपादक में बस अपने NSTableView या NSOutlineView का चयन करें और आकार इंस्पेक्टर में इस विकल्प का चयन करें:

फिर आप सेल को शीर्ष और निचली बाधाओं के लिए अपने NSTableCellView में सामान सेट करते हैं और आपका सेल स्वचालित रूप से फिट होने के लिए आकार बदल जाएगा। कोई कोड आवश्यक नहीं है!

आपका ऐप ऊंचाई heightOfRow ( NSTableView ) और heightOfRowByItem ( heightOfRowByItem ) में निर्दिष्ट किसी भी ऊंचाई को अनदेखा कर देगा। आप देख सकते हैं कि इस विधि के साथ आपकी ऑटो लेआउट पंक्तियों के लिए ऊंचाई की गणना की जा रही है:

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




osx-lion