Просмотр основанного редактирования NSTableView

Я не уверен, что я делаю неправильно. Поскольку я не мог найти никаких других вопросов (или даже документации) об этом, кажется, нормально работать без проблем для других людей.

Я просто пытаюсь получить NSTableView, основанный на представлении, для поддержки редактирования его содержимого. То есть приложение отображает NSTableView с одним столбцом и несколькими строками, содержащими NSTextField с некоторым контентом. Я хочу, чтобы (дважды) нажимал на ячейку и редактировал содержимое ячейки. Таким образом, в основном нормальное поведение NSTableView на основе ячейки, где реализован метод tableView:setObjectValue:forTableColumn:row:.

Я проанализировал пример Complex TableView в примере кода TableViewPlayground от Apple (который поддерживает редактирование содержимого ячеек), но я не могу найти параметр/код/​​переключатель, который позволяет редактировать.

Вот простой пример проекта (Xcode 6.1.1, SDK 10.10, основанный на раскадровке):

Заголовок:

#import <Cocoa/Cocoa.h>

@interface ViewController : NSViewController

@property (weak) IBOutlet NSTableView *tableView;

@end

Реализация:

#import "ViewController.h"

@implementation ViewController
{
    NSMutableArray* _content;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    _content = [NSMutableArray array];

    for(NSInteger i = 0; i<10; i++) {
        [_content addObject:[NSString stringWithFormat:@"Item %ld", i]];
    }
}

#pragma mark - NSTableViewDataSource
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
    return _content.count;
}

#pragma mark - NSTableViewDelegate
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
    NSTableCellView* cell = [tableView makeViewWithIdentifier:@"CellView" owner:self];
    cell.textField.stringValue = _content[row];
    return cell;
}

- (IBAction)endEditingText:(id)sender {
    NSInteger row = [_tableView rowForView:sender];
    if (row != -1) {
        _content[row] = [sender stringValue];
    }
}
@end

Файл раскадровки выглядит следующим образом: Storyboard

Источник данных и делегат представления таблицы установлены в контроллер представления. При запуске этого приложения в представлении таблицы отображаются 10 тестовых строк, но невозможно редактировать одну из строк.

Почему? Что я пропустил здесь?

Я дважды проверял все атрибуты NSTableView (и его содержимое) таким же, как в примере TableViewPlayground от Apple. И после нескольких часов поиска документации и Интернета для полезных советов без каких-либо успехов, я немного расстроен. Все, что вы можете найти на основе NSTableViews, являются незаменимыми образцами или очень неопределенной информацией о редактируемом контенте. И, конечно, есть тонны информации, документации и образцов для редактируемых, основанных на ячейках NSTableViews...

Почтовый ящик с моим примером проекта можно загрузить здесь: TableTest.zip

Ответ 1

По умолчанию каждая ячейка (экземпляр NSTableCellView) имеет на ней NSTextField. Когда вы редактируете ячейку, то вы редактируете это текстовое поле. Интерфейс Builder делает это текстовое поле недоступным для редактирования:

enter image description here

Все, что вам нужно сделать, - установить всплывающее окно "Поведение" на Editable. Теперь вы можете редактировать текстовое поле с обратным ударом или одиночный -click.

Ответ 2

Несмотря на то, что все части для редактирования NSTableView, основанного на представлении, присутствуют в вопросе и ответе, мне все еще не удалось собрать все это вместе. Следующая демонстрация находится в Swift, используя Xcode 6.3.2, но для пешеходов/женщин objective-C должно быть легко следовать. Полный список кодов находится в конце.

Начните здесь:

Справочник по протоколу NSTableViewDataSource

Значения настройки

- tableView: setObjectValue: forTableColumn: строка:

Swift:  
optional func tableView(_ aTableView: NSTableView,
              setObjectValue anObject: AnyObject?,
              forTableColumn aTableColumn: NSTableColumn?,
              row rowIndex: Int)

Objective-C:
- (void)tableView:(NSTableView *)aTableView
   setObjectValue:(id)anObject
   forTableColumn:(NSTableColumn *)aTableColumn
              row:(NSInteger)rowIndex

Обсуждение. Этот метод предназначен для использования с табличными представлениями в ячейках, он не должен использоваться с представлениями на основе представления. В таблицы, основанные на представлении, используйте цель/действие для установки каждого элемента в представлении клетка.

Если вы похожи на меня, вы просматривали протоколы NSTableViewDelegate и NSTableViewDataSource, которые искали какой-то метод редактирования. Однако обсуждение в приведенной выше цитате говорит вам, что вещи намного проще.

1) Если вы посмотрите в схеме документа для вашего TableView в Xcode, вы увидите что-то вроде этого:

enter image description here

Ячейка в TableView представлена ​​ Table Cell View. Ячейка содержит несколько элементов, и по умолчанию одним из элементов является NSTextField. Но где NSTextField в контуре документа?! Ну, элементы управления в контуре документа имеют значок, который выглядит как слайдер рядом с их именами. Взглянуть. Внутри ячейки вы увидите что-то, у которого есть значок слайдера рядом с ним. И, если вы выберете эту строку в контуре документа, затем откройте Identity Inspector, вы увидите, что это NSTextField:

enter image description here

Вы можете считать это просто обычным старым NSTextField.

При реализации методов протокола NSTableViewDataSource:

import Cocoa

class MainWindowController: NSWindowController,
                            NSTableViewDataSource {
    ...
    ...
    var items: [String] = []  //The data source: an array of String's
    ...
    ...

    // MARK: NSTableViewDataSource protocol methods

    func numberOfRowsInTableView(tableView: NSTableView) -> Int {
        return items.count
    }

    func tableView(tableView: NSTableView,
                   objectValueForTableColumn tableColumn: NSTableColumn?,
                   row: Int) -> AnyObject? {
        return items[row]
    }

}

.. TableView принимает значение, возвращаемое вторым методом, и присваивает ему свойство с именем objectValue во внешнем Table Cell View - другими словами, TableView не использует возвращаемое значение для установки NSTextField (т.е. внутренний Table View Cell). Это означает, что ваши элементы источника данных не будут отображаться в TableView, потому что NSTextField отображает элемент. Чтобы установить значение NSTextField, вам необходимо связать или привязать NSTextField value к свойству objectValue. Вы делаете это в инспекторе Bindings:

Предупреждение.. Не устанавливайте флажок Bind to до тех пор, пока не выберете объект, к которому вы хотите привязать. Если вы проверите флажок сначала, объект будет вставлен в схему вашего документа, и если вы этого не заметите, вы получите ошибки при запуске программа. Если вы случайно проверите флажок Bind to, сделайте обязательно удалите автоматически добавленный объект в документе контур. Xcode создает отдельный раздел для добавленного объекта, поэтому его легко обнаружить в контуре вашего документа.

enter image description here

2) Отказываясь на мгновение, вы, вероятно, знакомы с подключением кнопки к методу действий, а после этого, если вы нажмете на кнопку, действие будет выполнено. С другой стороны, с помощью NSTextField вы обычно объявляете IBOutlet, который затем используется для получения или установки NSTextField stringValue.

Однако NSTextField также может инициировать выполнение метода действия. Wha??! Но вы не можете нажать на поле NSText, как вы можете кнопку! Тем не менее, у NSTextField есть триггер, как нажатие кнопки, что приведет к выполнению метода действия, а триггер: закончить редактирование NSTextField. Как NSTextField знает, когда вы закончите его редактирование? Существует два способа:

  • Вы нажмете Return.

  • Вы нажимаете на другой элемент управления.

Вы можете выбрать триггер в Инспекторе атрибутов:

enter image description here

3) Как показал в своем ответе @Paul Patterson, следующее, что вам нужно сделать, - установить NSTextField Behavior в Editable в Attributes Inspector.

4) Затем подключите NSTextField к методу действий, который вы хотите выполнить. Если вы не использовали следующий трюк для подключения элемента управления к методу действий, вы должны попробовать его некоторое время:

Выберите файл .xib в Навигаторе проектов, чтобы окно и отображаются его элементы управления. Затем нажмите "Редактор помощника" ( two_interlocking_rings в верхней части окна Xcode справа) - отобразит ваш файл контроллера (если будет показан другой файл, затем используйте панель перехода, чтобы перейти к файлу вашего контроллера). Затем Control + drag от NSTextField (в контуре документа) до места в вашем Файл контроллера, в котором вы хотите создать свой метод действий:

enter image description here

Когда вы отпустите, вы увидите это всплывающее окно:

enter image description here

Если вы введете ту же информацию, что и показано, в файл будет введен следующий код:

@IBAction func onEnterInTextField(sender: NSTextField) {

}

И... связь между NSTextField и действием будет уже выполнена. (Вы также можете использовать эти шаги для создания и подключения IBOutlet.)

5) Внутри метода действий вы можете получить текущую выбранную строку, то есть только что отредактированную, из TableView:

@IBAction func onEnterInTextField(sender: NSTextField) {    
    let selectedRowNumber = tableView.selectedRow  //tableView is an IBOutlet connected to the NSTableView

}

Затем я был озадачен тем, как получить текст выбранной строки в TableView, и обратно в документы, которые я просматривал, просматривая методы TableView и протокола. Но мы все знаем, как получить stringValue из NSTextField, не так ли? Отправитель - это NSTextField, который вы редактировали:

@IBAction func onEnterInTextField(sender: NSTextField) {
    let selectedRowNumber = tableView.selectedRow  //My Controller has  an IBOutlet property named tableView which is connected to the TableView 

    if selectedRowNumber != -1 {  //-1 is returned when no row is selected in the TableView
        items[selectedRowNumber] = sender.stringValue  //items is the data source, which is an array of Strings to be displayed in the TableView
    }

}

Если вы не введете новое значение в источник данных, то в следующий раз, когда TableView должен отобразить строки, исходное значение снова отобразится, перезаписав отредактированные изменения. Помните, что TableView извлекает значения из источника данных - не NSTextField. Затем NSTextField отображает любое значение, которое TableView присваивает свойству cellValue ячейки.

Последняя вещь. У меня появилось предупреждение о том, что я не мог подключить NSTextField к действию внутри класса, если класс не был делегатом TableView.... так что я подключил выход делегата TableView к файловому владельцу:

enter image description here

Ранее я установил File Owner как My Controller (= MainWindowController), поэтому после того, как я сделал это соединение, MainWindowController, содержащий метод действия для NSTextField, стал делегатом TableView, а предупреждение ушел.

Случайные советы:

1) Я нашел самый простой способ начать редактирование NSTextField - выбрать строку в TableView, а затем нажать Return.

2) NSTableView поставляется с двумя столбцами по умолчанию. Если вы выберете один из столбцов в контуре документа, нажмите "Удалить" на клавиатуре, вы можете создать одну таблицу столбцов, однако TableView по-прежнему показывает разделитель столбцов, поэтому похоже, что еще есть два столбца. Чтобы избавиться от разделителя столбцов, выберите Bordered Scroll View - Table View в контуре документа, а затем перетяните один из углов, чтобы изменить размер TableView - один столбец мгновенно изменит размер, чтобы охватить все свободное пространство.

Кредит для шагов №1 и №2 и Случайный совет №2: Cocoa Программирование для OS X (5-е издание, 2015).

Полный список кодов:

//
//  MainWindowController.swift
//  ToDo
//

//import Foundation
import Cocoa

class MainWindowController: NSWindowController,
                            NSTableViewDataSource {

    //@IBOutlet var window: NSWindow? -- inherited from NSWindowController
    @IBOutlet weak var textField: NSTextField!
    @IBOutlet weak var tableView: NSTableView!

    var items: [String] = []  //The data source: an array of String's

    override var windowNibName: String {
        return "MainWindow"
    }

    @IBAction func onclickAddButton(sender: NSButton) {
        items.append(textField.stringValue)
        tableView.reloadData()  //Displays the new item in the TableView

    }

    @IBAction func onEnterInTextField(sender: NSTextField) {
        let selectedRowNumber = tableView.selectedRow

        if selectedRowNumber != -1 {
            items[selectedRowNumber] = sender.stringValue
        }
    }


    // MARK: NSTableViewDataSource protocol methods

    func numberOfRowsInTableView(tableView: NSTableView) -> Int {
        return items.count
    }

    func tableView(tableView: NSTableView,
                   objectValueForTableColumn tableColumn: NSTableColumn?,
                   row: Int) -> AnyObject? {
        return items[row]
    }

 }

Инспектор подключений, показывающий все подключения для File Owner (= MainWindowController):

enter image description here

Ответ 3

Просто исправление принятого ответа - вы должны получить строку и столбец, используя tableView.row (для: NSView) и tableView.column (для: NSView. Другие методы могут быть ненадежными.

@IBAction func textEdited(_ sender: Any) {
        if let textField = sender as? NSTextField {

            let row = self.tableView.row(for: sender as! NSView)
            let col = self.tableView.column(for: sender as! NSView)
            self.data[row][col] = textField.stringValue

            print("\(row), \(col), \(textField.stringValue)")

            print("\(data)")
        }
    }