Как создать NSCollectionView программно с нуля? - PullRequest
32 голосов
/ 28 декабря 2011

NSCollectionView остается одной из самых загадочных частей API Какао, которую я когда-либо видел. Документация плохая, и есть много движущихся частей, многие из которых часто реализуются в Интерфейсном Разработчике, что усложняет документацию.

Пожалуйста, предоставьте пример кода для создания простейшего случая NSCollectionView, который отображает либо текстовые поля, либо кнопки без использования Xcode, где каждое текстовое поле или кнопка имеет свой заголовок. Предположим, что новый проект XCode со значением по умолчанию window IBOutlet.

В этом примере не требуется привязка для обновления NSCollectionView при изменении источника данных. Просто отобразите сетку объектов-прототипов и установите для каждого объекта заголовок определенного значения.

Если мы сможем получить хороший пример того, как сделать это доступным для многих людей, я думаю, что это поможет всем, кто работает с NSCollectionViews и так же озадачен, как и я.

Резюме запроса

  • Предоставить пример кода для визуализации NSCollectionView в новом проекте Xcode
  • Не используйте Interface Builder, используйте окно IBOutlet по умолчанию
  • NSCollectionView должен содержать текстовые поля или кнопки, на ваш выбор
  • Каждый элемент в представлении должен иметь различный заголовок
  • Связывание не требуется

Если есть пример кода, который отвечает этим требованиям, предоставьте ссылку, это было бы здорово!

Ответы [ 3 ]

59 голосов
/ 06 февраля 2012

Я не уверен, что есть много идей для создания представления коллекции программно и без привязок, но здесь все идет.

Введение

Существуют четыре компонента при использовании представления коллекции:

  • Представление: подкласс NSView, отвечающий за отображение информации;
  • Само представление коллекции;
  • Контроллер представления: подкласс NSCollectionViewItem, которыйслужит прототипом элемента представления коллекции;
  • Модель: массив объектов.

Обычно представление создается в Интерфейсном Разработчике, а модель опосредуется привязками Какао.

Выполнение этого программным способом:

Константы

static const NSSize buttonSize = {80, 20};
static const NSSize itemSize = {100, 40};
static const NSPoint buttonOrigin = {10, 10};

Представление

Это стандартное представление (настраиваемое представление на языке Interface Builder), содержащее кнопку.Обратите внимание, что представление имеет фиксированный размер.

@interface BVView : NSView
@property (weak) NSButton *button;
@end

@implementation BVView
@synthesize button;
- (id)initWithFrame:(NSRect)frameRect {
    self = [super initWithFrame:(NSRect){frameRect.origin, itemSize}];
    if (self) {
        NSButton *newButton = [[NSButton alloc] 
            initWithFrame:(NSRect){buttonOrigin, buttonSize}];
        [self addSubview:newButton];
        self.button = newButton;
    }
    return self;
}
@end

View Controller (Prototype)

Обычно контроллер представления загружает свое представление из файла пера.В тех редких случаях, когда контроллер представления не получает свое представление из nib-файла, разработчик должен либо отправить его -setView: до получения -view контроллером представления, либо переопределить -loadView.Следующий код выполняет последнее.

Контроллеры представления получают соответствующий объект модели через -setRepresentedObject:.Я переопределил его, чтобы обновлять заголовок кнопки при каждом изменении объекта модели.Обратите внимание, что это может быть достигнуто путем использования привязок Какао без какого-либо кода вообще.

Обратите внимание, что ни один из этого кода не является специфическим для представлений коллекции - это общее поведение контроллера представления.

@interface BVPrototype : NSCollectionViewItem
@end

@implementation BVPrototype
- (void)loadView {
    [self setView:[[BVView alloc] initWithFrame:NSZeroRect]];
}
- (void)setRepresentedObject:(id)representedObject {
    [super setRepresentedObject:representedObject];
    [[(BVView *)[self view] button] setTitle:representedObject];
}
@end

Модель

Простой массив строк, представляющих заголовки кнопок:

@property (strong) NSArray *titles;
self.titles = [NSArray arrayWithObjects:@"Case", @"Molly", @"Armitage",
    @"Hideo", @"The Finn", @"Maelcum", @"Wintermute", @"Neuromancer", nil];

Представление коллекции

Пока единственное установленное отношение - это используемое представление (BVView)по прототипу изделия (BVPrototype).Представление сбора должно быть проинформировано о прототипе, который он должен использовать, а также о модели, из которой следует получать данные.

NSCollectionView *cv = [[NSCollectionView alloc]
    initWithFrame:[[[self window] contentView] frame]]; 
[cv setItemPrototype:[BVPrototype new]];
[cv setContent:[self titles]];

Полный исходный код для делегата приложения

#import "BVAppDelegate.h"


static const NSSize buttonSize = { 80, 20 };
static const NSSize itemSize = { 100, 40 };
static const NSPoint buttonOrigin = { 10, 10 };


@interface BVView : NSView
@property (weak) NSButton *button;
@end

@implementation BVView
@synthesize button;
- (id)initWithFrame:(NSRect)frameRect {
    self = [super initWithFrame:(NSRect){frameRect.origin, itemSize}];
    if (self) {
        NSButton *newButton = [[NSButton alloc]
            initWithFrame:(NSRect){buttonOrigin, buttonSize}];
        [self addSubview:newButton];
        self.button = newButton;
    }
    return self;
}
@end


@interface BVPrototype : NSCollectionViewItem
@end

@implementation BVPrototype
- (void)loadView {
    [self setView:[[BVView alloc] initWithFrame:NSZeroRect]];
}
- (void)setRepresentedObject:(id)representedObject {
    [super setRepresentedObject:representedObject];
    [[(BVView *)[self view] button] setTitle:representedObject];
}
@end


@interface BVAppDelegate ()
@property (strong) NSArray *titles;
@end

@implementation BVAppDelegate

@synthesize window = _window;
@synthesize titles;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    self.titles = [NSArray arrayWithObjects:@"Case", @"Molly", @"Armitage",
        @"Hideo", @"The Finn", @"Maelcum", @"Wintermute", @"Neuromancer", nil];

    NSCollectionView *cv = [[NSCollectionView alloc]
        initWithFrame:[[[self window] contentView] frame]]; 
    [cv setItemPrototype:[BVPrototype new]];
    [cv setContent:[self titles]];

    [cv setAutoresizingMask:(NSViewMinXMargin
                             | NSViewWidthSizable
                             | NSViewMaxXMargin
                             | NSViewMinYMargin
                             | NSViewHeightSizable
                             | NSViewMaxYMargin)];
    [[[self window] contentView] addSubview:cv];
}

@end
5 голосов
/ 22 декабря 2015

@ Bavarious Вы проделали отличную работу там.Это было просто удивительное руководство, которое я иногда пропускаю в Apple Docs.

Я переписал Bavarious 'код в Swift (v2) для всех, кому это интересно:

// AppDelegate.swift:

import Cocoa

let buttonSize:NSSize = NSSize(width: 80, height: 20)
let itemSize:NSSize = NSSize(width: 100, height: 40)
let buttonOrigin:NSPoint = NSPoint(x: 10, y: 10)

let titles:[String] = ["Case", "Molly", "Armitage", "Hideo", "The Finn", "Maelcum", "Wintermute", "Neuromancer"]

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!

    func applicationDidFinishLaunching(aNotification: NSNotification) {
        let cv = NSCollectionView(frame: self.window.contentView!.frame)
        cv.itemPrototype = BVTemplate()
        cv.content = titles

        cv.autoresizingMask = NSAutoresizingMaskOptions.ViewMinXMargin
            .union(NSAutoresizingMaskOptions.ViewWidthSizable)
            .union(NSAutoresizingMaskOptions.ViewMaxXMargin)
            .union(NSAutoresizingMaskOptions.ViewMinYMargin)
            .union(NSAutoresizingMaskOptions.ViewMaxYMargin)
            .union(NSAutoresizingMaskOptions.ViewHeightSizable)

        window.contentView!.addSubview(cv)
    }

    func applicationWillTerminate(aNotification: NSNotification) {
        // Insert code here to tear down your application
    }
}

// BVTemplate.swift:

import Cocoa

class BVTemplate: NSCollectionViewItem {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do view setup here.
    }

    override func loadView() {
        print("loadingView")
        self.view = BVView(frame: NSZeroRect)
    }

    override var representedObject:AnyObject? {
        didSet {
            if let representedString = representedObject as? String {
                (self.view as! BVView).button?.title = representedString
            }
        }
    }
}

// BVView.swift:

import Cocoa

class BVView: NSView {

    var button:NSButton?

    override init(frame frameRect: NSRect) {
        super.init(frame: NSRect(origin: frameRect.origin, size: itemSize))
        let newButton:NSButton = NSButton(frame: NSRect(origin: buttonOrigin, size: buttonSize))
        self.addSubview(newButton)
        self.button = newButton
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
}
4 голосов
/ 12 ноября 2012

Чтобы ответить на вопрос бригадира о том, как связать с изменяемым массивом.

zero'th - сначала сделать заголовки NSMutableArray

- связать массив с вашими элементами

[cv bind:NSContentBinding 
    toObject:self 
    withKeyPath:@"titles" 
    options:NULL];

Второе - при изменении заголовков обязательно измените прокси.

например

NSMutableArray *kvcTitles = [self mutableArrayValueForKey:@"titles"];
[kvcTitles removeLastObject];
...