Показаны сообщения с ярлыком Cocoa. Показать все сообщения
Показаны сообщения с ярлыком Cocoa. Показать все сообщения

24 мая 2014 г.

16 мая 2014 г.

NSCollectionView без байндингов - пример / NSCollectionView without bindings

NSCollectionView — это класс, позволяющий показывать на экране коллекцию айтемов. Структура коллекции — абсолютно произвольная, но обычно NSCollectionView используется для всяких сетко-подобных контролов с ячейками, хедерами и футерами. Понимая, насколько абстрактен данный класс, разработчики Apple создали мощный механизм для создания любых лейаутов. По большому счету, даже NSTableView это конкретная реализация NSCollectionView  Возможности данного класса, в каком-то смысле, фантастические.
На просторах сети очень много примеров по созданию NSCollectionView с байндингами (NSCollectionView with bindings). Как бы все хорошо, все работает, но вопрос в том как оно работает?

В NSCollectionView также как и в NSTableView есть парочка важных методов, без которых ничего работать не будет, т.е. будет, но ничего не выдаст.

Метод -setPrototype - в него нужно передать экземпляр сабкласса NSCollectionViewItem.

@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

Метод -setContent - в него нужно передать массив модели.

@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

Метод -setRepresentedObject - (указан в коде выше) в него передаем нашу модель.

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

Документация по NSCollectionView сама по себе немного скудновата, но если хорошо пошерстить вэб-сеть, то можно что-то и нарыть.

В следующих статьях под тегом "Мой проект", я опишу как можно создать NSCollectionView на основе NSView. В принципе там не так уж и сложно, почти как с таблицами в предыдущих постах.

6 мая 2014 г.

3 мая 2014 г.

NSMutableString - пример

Класс NSString используется для хранения строк, но в нем присутствует небольшой недостаток. Мы не можем изменять строку которая хранится в экземпляре этого класса. С этой проблемой очень хорошо справляется класс NSMutableString. У него намного больше возможностей обработки строк в отличии от NSString.

Рассмотрим небольшой пример:
Создадим экземпляр класса NSMutableString.

NSMutableString *stringOne = [NSMutableString initWithCapacity: 15];
NSMutableString *stringTwo = [[NSMutableString alloc] initWithCapacity: 30];

В первом и втором примере мы создаем экземпляр класса с выделением под строку определенное количество символов (15 и 30). Второй пример характерен тем, что, мы сами контролируем процесс выделения и очистки памяти. Что произойдет если мы к выделенному количеству символов добавим еще некое количество и случайно выйдем за пределы выделенного количества символов? Ничего не произойдет. Приложение будет и дальше работать также само. Этот параметр носит характер оптимизации для компилятора по выделении памяти и значение этого параметра никак не влияет на длину строки.

По-практикуем:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        
        NSMutableString *mtStr = [[NSMutableString alloc] initWithCapacity:15];
        [mtStr appendString:@"— Будете у нас, на Колыме – милости просим!"];
        NSLog(@"%@", mtStr);
        
        [mtStr appendFormat:@" — Нет,%@", @" уж лучше вы к нам!"];
        NSLog(@"%@", mtStr);
        
        NSRange strRng = [mtStr rangeOfString:@" — Нет,"];
        [mtStr deleteCharactersInRange:strRng];
        NSLog(@"%@", mtStr);
        
        //[mtStr insertString:@" - Да" atIndex:43];
        //NSLog(@"%@", mtStr);
        
        [mtStr insertString:@" - Да" atIndex:strRng.location];
        NSLog(@"%@", mtStr);
        
        [mtStr setString:@"Новая строка"];
        NSLog(@"%@", mtStr);
    }
    return 0;
}

Результат выполнения:


29 апр. 2014 г.

Категории - теория и пример

Язык Objective-C обладает возможностью добавлять новые методы к уже существующим классам (т.е. расширение функциональности класса). При этом не требуется исходников класса и добавленные методы автоматически становятся доступными всем классам, унаследованным от изменяемого. Так можно добавить новый метод классу NSString (возьмем за пример) и этот метод автоматически добавится во все остальные классы.

Механизм, позволяющий расширять уже существующие классы (путем добавления новых методов, новые instance-переменные добавить таким образом нельзя), называется категорией.

Категория имеет свое имя, список методов и имя класса, который она расширяет. Описание категории имеет следующий вид:

#import "ClassName.h"
@interface ClassName (CategoryName)
  // объявление методов
@end

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

#import "CategoryName.h"
@implementation ClassName (CategoryName)
  // реализация методов
@end

Ограничения при создании категорий:
- Невозможность добавления переменных;
- Возможная коллизия имен с самим классом, поэтому необходимо использовать оригинальные префиксы в наименовании своих методов.

Пример.
Расширим класс NSString, добавим метод который будет проверять является ли файл аудио-файлом. Создадим проект Foundation и добавим в проект новый класс-категорию NSStringAudioExtension (наследуемый от NSString):


Объявим в NSStringAudioExtension.h метод - (BOOL) isAudioFile:

#import <Foundation/Foundation.h>

@interface NSString (NSStringAudioExtension)

- (BOOL) isAudioFile;

@end

В NSStringAudioExtension.m реализуем метод:

#import "NSString+NSStringAudioExtension.h"

@implementation NSString (NSStringAudioExtension)

- (BOOL) isAudioFile {
    
    if ([self hasSuffix:@".mp3"] || [self hasSuffix:@".wav"] || [self hasSuffix:@".arm"]) {

        return YES;

    }
    
    return NO;
}

@end

Дальше возвращаемся в main.m (технически класс NSString должен подхватить его расширение):

#import <Foundation/Foundation.h>
#import "NSString+NSStringAudioExtension.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        
        NSString *audioFileStr = @"Scooter feat. Vicky Leandros - C'est Bleu.mp3";
        
        if ([audioFileStr isAudioFile]) {
            
            NSLog(@"Да, это аудио-файл");
            
        } else {
            
            NSLog(@"Нет, это не аудио-файл");
            
        }
        
    }
    return 0;
}

Результат выполнения приложения:


Заменим .mp3 на .mp4, результат выполнения:


14 апр. 2014 г.

View-based NSTableView на основе ячеек из NSView

Суть построения View-based таблицы заключается в том, чтобы вместо обычных строк, состоящих из NSImageView и NSTextField (как показано в предыдущем примере), создать кастомную (свою модель компонентов в строке) строку. И здесь как всегда на помощь приходит NSView, а точнее NSViewController - класс.

Создаем новый проект. Назовем его ViewBasedNSTableViewWithNSView (Вы можете его назвать по своему, суть этого не меняется). Как и в предыдущем примере добавим таблицу, сделаем строку View-based (прочтите предыдущий пример, здесь в этом посте я упущу эти тонкости). Далее добавим новый класс наследуемый от NSViewController c xib-формой. Добавим компоненты интерфейса на  xib-форму. Я добавил два Label, кнопку с изображением ImageCell и ProgressBar. Т.е. можно накидать компоненты по своему желанию и усмотрению, а также целевому типу. Должно получится что-то из этого:


В ViewController.h сделаем оутлеты к нашим компонентам и к самой вьюхе и парочку методов.


#import <Cocoa/Cocoa.h>

@interface ViewController : NSViewController {
    
    IBOutlet NSImageView *iconView;
    IBOutlet NSTextField *messageLabelOne;
    IBOutlet NSTextField *messageLabelTwo;
    NSView *_mainView;
    
}

- (void) setStringValueForFirstTextFields: (NSString *) stringValue;
- (void) setStringValueForSecondTextFields: (NSString *) stringValue;
- (void) setIconForRowOnView: (NSImage *) iconContent;

@end


В ViewController.m распишем код для наших методов, также по умолчанию зададим текст для лэйблов:


#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Initialization code here.
    }
    return self;
}

- (id)init {
    
    [self.view addSubview: _mainView];
            
    [messageLabelOne setStringValue:@"Message One"];
    [messageLabelTwo setStringValue:@"Message Two"];
    
    return self;
}

- (void) setStringValueForFirstTextFields: (NSString *) stringValue {
    
    [messageLabelOne setStringValue:stringValue];
    
}

- (void) setStringValueForSecondTextFields: (NSString *) stringValue {
    
    [messageLabelTwo setStringValue:stringValue];
    
}

- (void) setIconForRowOnView: (NSImage *) iconContent {
    
    [iconView setImage:iconContent];
    
}

@end


Подключим хидер контроллера-модели ячейки в AppDelegate.h. Объявим массив в котором будем хранить вьюхи-объекты, свяжем оутлет переменной NSTableView с таблицей на форме, добавим объект-контроллер (по которому будем обращаться к индексу массива объектов и получать наш объект для текущего индекса строки. Помните?, я уже упоминал что строки в таблицу добавляются строчка-за-строчкой.).


#import <Cocoa/Cocoa.h>
#import "ViewController.h"

@interface AppDelegate : NSObject <NSApplicationDelegate, NSTableViewDelegate, NSTableViewDataSource> {
    
    NSMutableArray *objArray;
    ViewController *vController;
}

@property (assign) IBOutlet NSWindow *window;
@property (weak) IBOutlet NSTableView *tableView;

@end


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


#import "AppDelegate.h"

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // Insert code here to initialize your application
}

- (void)awakeFromNib {
    
    objArray = [[NSMutableArray alloc] init];
    
    ViewController *oneController = [[ViewController alloc] init];
    ViewController *twoController = [[ViewController alloc] init];
    ViewController *treeController = [[ViewController alloc] init];
    ViewController *fourController = [[ViewController alloc] init];
    ViewController *fiveController = [[ViewController alloc] init];
    ViewController *sixController = [[ViewController alloc] init];
    ViewController *sevenController = [[ViewController alloc] init];
    
    [oneController setStringValueForFirstTextFields:@"Hello World"];
    [oneController setStringValueForSecondTextFields:@"Hello To You"];
    [oneController setIconForRowOnView:[NSImage imageNamed:@"layers.icns"]];
    
    [twoController setStringValueForFirstTextFields:@"Hello World"];
    [twoController setStringValueForSecondTextFields:@"Hello To You"];
    [twoController setIconForRowOnView:[NSImage imageNamed:@"Slice 1.icns"]];
    
    [objArray addObject:oneController];
    [objArray addObject:twoController];
    [objArray addObject:treeController];
    [objArray addObject:fourController];
    [objArray addObject:fiveController];
    [objArray addObject:sixController];
    [objArray addObject:sevenController];
}

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
    
    return [objArray count];
}

- (id)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    
    vController = [objArray objectAtIndex:row];
    return [vController view];
    
}

@end


Должна получится вот такая каша:
В принципе все. Самое главное - это понять как работает делегирование в таблице и сама таблица. Все остальное дело техники. При моей занятости (работа, семья) на понимание всей этой каши (по работе с таблицами в целом) ушло месяц-полтора. Если посчитать время уделенное на разборку всего этого, то общая затраченная сумма при интенсивном изучении заняла бы всего-то меньше недели.

Продолжение

1 апр. 2014 г.

NSUserDefaults - сохранение настроек приложения

Использование NSUserDefaults - самый легкий и самый простой способ (существуют и иные способы) сохранения настроек Вашего приложения.

NSUserDefaults - это простой список данных (аналог plist), с помощью которого приложение может хранить простые данные. Нет никаких ограничений на размер данных (помимо собственных ограничений заданных приложением), не используйте этот класс для хранения очень большого объема данных. Файл записывается и считывается атомарно (т.е. целиком), т.е., чем больше данных, которые находятся в файде, тем дольше они будут считыватся. Тем не менее, этот класс подходит для того, чтобы хранить параметры, настройки, и т.п..

К сожалению NSUserDefaultsимеет ряд ограничений по переменным, которые он может сохранять. Это:
– NSArray
– NSData
– NSDictionary
– NSNumber
– NSString

Кроме того, NSArray или NSDictionary должны только содержать упомянутые выше типы (возможно вложение NSArray или NSDictionary). Другие пункты, которые соответствуют протоколу NSCoding, могут быть заархивированы как NSData, т.е., Вы можете сохранить их в переменных. Будем использовать ключи для доступа к данным.

Все что нужно сделать, это загрузить данные в NSUserDefault:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setInteger:9001 forKey:@"HighScore"];
[defaults synchronize];

Чтение данных из NSUserDefault:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSInteger theHighScore = [defaults integerForKey:@"HighScore"];

Вот и все. Вы создаете объект типа NSUserDefaults, создаете переменную в которою будете загружать данные по ее типу и определенному ключу.

31 мар. 2014 г.

Компонент интерфейса в заголовке окна

В обычной программе за отрисовку окна отвечает недокументированный класс NSThemeFrame.
Создаем окно с оутлетом window на это окно. Добавляем NSView и тоже создаем на него привязку оутлет:
Добавляем на вьюху компонент NSPopupButton, убираем флажок Bordered в Инспекторе Атрибутов. В AppDelegate.h должен быть такой код:

#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate> {
    
    IBOutlet NSWindow *window;
    IBOutlet NSView *itemView;
}

- (void)composeInterface;

@end


AppDelegate.m:

#import "AppDelegate.h"

@implementation AppDelegate

- (void)awakeFromNib
{
[self composeInterface];
}

- (void)composeInterface
{
// Получаем указатель на фрейм окна
NSView *themeFrame = [[window contentView] superview];
NSRect contentWindowFrame = [themeFrame frame]; // размер фрейма окна
NSRect itemViewFrame = [itemView frame]; // размер фрейма вьюхи
NSRect newFrame = NSMakeRect(
                                 contentWindowFrame.size.width - itemViewFrame.size.width, // x позиция
                                 contentWindowFrame.size.height - itemViewFrame.size.height, // y позиция
                                 itemViewFrame.size.width, // ширинв
                                 itemViewFrame.size.height); // высота
    
[itemView setFrame:newFrame];
[themeFrame addSubview:itemView];
}

@end


Вместо NSPopupButton можна добавить любой элемент на вьюху. Главное чтобы высота компонента не была больше чем сам Title Bar.
Дальше все просто. Делаем оутлет на PopupButton, и делаем с ним все что душе угодно.

26 мар. 2014 г.

View-based NSTableView пример

Виды на основе view-based таблицы, обеспечивают богатые возможности во время проектирования. Сам по себе NSTableCellView отображает ImageView и textField. Но отличающейся особенностью этого рода view-based ячеек от cell-based ячеек, является размещение в ячейке разных видов компонентов интерфейса.

Итак, начнем. Создадим новый проект в Xcode. Добавим на форму NSTableView. Выберем нашу таблицу NSTableView (Помните, что таблица сама по себе состоит из набора компонентов - NSScrollView -> NSClipView -> NSTableView-> NSTableColumn -> NSCell). И в Инспекторе Атрибутов в секции Table View -> Content Mode, вместо Cell Based выберем View Based. Выставим количество колонок равным 1-й. Удалим в таблице строчку Text View или Text, и вместо этой строчки добавим View-based строчку (В наборе компонентов интерфейса находим Image & Text Table Cell View, и перетаскиваем в нашу таблицу). Выделяем вставленную строку и в Size Inspector устанавливаем высоту строки равным 50px. Далее выбираем иконку в нашей ячейке и в Инспекторе Атрибутов в секции Image Cell -> Image наберем NSApplicationIcon (Вы можете растянуть иконку как Вам угодно, и скомпоновать с текстовой строкой в разных положениях в пределах View-строки). Напоследок выделяем NSTableColumn, и в Identity Inspector -> Identity -> Identifier пропишем идентификатор колонки "MainCell". По этому идентификатору будем обращаться к колонке не по ее ID, а по ее имени, что естественно проще запомнить. Должно получится что-то вроде этого:

Опять выделим NSTableView. Перейдем в Connections Inspector и свяжем dataSource и delegate с App Delegate - объектом.

Суть задачи состоит в том, чтобы из стандартного приложения iChat вытянуть флаги стран - собеседников и вывести их в таблицу отобразив иконку флага и название страны.

В AppDelegate.h добавляем делегаты NSTableViewDataSource и NSTableViewDelegate, потому что Мы будем использовать методы делегатов для работы с таблицами. Основные методы, без которых не будет работать таблица описаны в статье Урок по NSTableView.

@interface AppDelegate : NSObject <NSApplicationDelegate, NSTableViewDataSource, NSTableViewDelegate>

Открываем AppDelegate.m и пишем код (код с пояснениями):

#import "AppDelegate.h"

@implementation AppDelegate {
    
    NSMutableArray *tableContents;
}

- (void)awakeFromNib {
    
    tableContents = [[NSMutableArray alloc] init];
    
    // Устанавливаем путь к файлам - флагам
    NSString *pathFlags = @"/Library/Application Support/Apple/iChat Icons/Flags";
    
    // Инициализируем файл - менеджер для работы с файлами
    NSFileManager *filesOfFlags = [NSFileManager defaultManager];
    
    // Получает список поддиректорий директории или список файлов
    NSDirectoryEnumerator *dEnumerator = [filesOfFlags enumeratorAtPath:pathFlags];
    
    // Переменная в которой будет хранится каждый файл
    NSString *file;
    
    // Проходим в цикле по всем файлам и добавляем созданные объекты в массив
    while (file = [dEnumerator nextObject]) {
        
        // Получаем полное имя файла с расширением
        NSString *filePath = [pathFlags stringByAppendingFormat:@"/%@", file];
        
        // Вытягиваем рисунок с файла и имя файла без расширения
        // Инициализируем для каждого изображения и имени ключи по которым будем обращаться
        // к компонентам объекта
        NSDictionary *dictObj = @{@"image": [[NSImage alloc] initByReferencingFile:filePath],
                                  @"imageName": [file stringByDeletingPathExtension]};
        
        // Добавляем объект словаря в массив
        [tableContents addObject:dictObj];
    }
}

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
    
    // Возвращаем количество строк в массиве для выделения количества строк в таблице
    return [tableContents count];
}

- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    
    // Получаем входящую строку row (ее индекс)
    NSDictionary *flags = tableContents [row];
    
    // Определяем идентификатор таблицы (MainCell)
    NSString *colID = [tableColumn identifier];
    
    if ([colID isEqualToString:@"MainCell"]) {
        
        // Получить ячейку с идентификатором MainCell если она существует
        NSTableCellView *tCell = [tableView makeViewWithIdentifier:@"MainCell" owner:self];
        
        // Заполняем ячейки таблицы
        [tCell.textField setStringValue:flags[@"imageName"]];
        [tCell.imageView setImage:flags[@"image"]];
        
        // Возвращаем готовую ячейку, иначе нихрена не делаем
        return tCell;
    }
    
    return nil;
}

@end

Если все правильно сделано, должно в итоге получится во такое приложение:

В папке (к которой Мы обращаемся через код) лежат файлы типа .png. Т.е. Мы загрузили рисунок, получили имя файла, отсекли его расширение.

24 мар. 2014 г.

23 мар. 2014 г.

NSPopover и detachableWindowForPopover пример

NSPopover является анимированный всплывающий компонент, который показывает загруженные файлы в вэб-браузере Safari. Такой компонент был добавлен ​​в Mac OS X Lion. Если вы когда-нибудь задумывались, как добавить его в код, то это очень просто.

Принцип работы состоит в том, что popover связывает NSView с определенной позицией в окне (с позицией вызывающего контрола интерфейса на форме). NSView содержит пользовательский материал который будет виден в popover.

Начнем. У нас есть окно. Добавим на окно нашей формы кнопку, по которой мы будем вызывать наш popover.


Также добавим NSView, на котором разместим иконку (также можно любой контрол, будь-то кнопку или текстовое сообщение).


Найдем в списке библиотеки объектов Popover и перетянем в наш список объектов (вместе с Popover автоматически будет перетянут и Popover View Controller).

Теперь свяжем наш NSView с Popover View Controller и выберем оутлет view.
Также создадим для кнопки экшен, из которой будет вызываться popover.

- (IBAction)showPopOver:(id)sender;

И создадим связку нашего Popover (в списке наших объектов) с оутлетом *popover.


@property (assign) IBOutlet NSPopover *popover;


В экшен кнопки добавим такой код:


[[self popover] showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxXEdge];


Здесь макрос NSMaxXEdge будет показывать наш popover вертикально сверху от вызывающего контрола или снизу (в зависимости от вертикального расположения нашего окна). Если нужно показывать слева или справа - NSMaxYEdge.

Выделим наш Popover и в инспекторе атрибутов и в Popover -> Behavior выберем Transient. Этот атрибут будет позволять popover`у удаляться если кликнуть где-то в другом месте окна приложения.



Вот что должно получится:
detachableWindowForPopover - popover который при перетаскивании может преобразовываться в окошко NSWindow. Все тоже очень просто. Добавляем в наш список объектов NSWindow. Размещаем на нем что-то, можно ту же иконку.  Выделяем наш popover и связываем delegate (popover`а) с App Delegate - объектом (в списке наших объектов). Теперь в хидер файл добавим NSPopoverDelegate делегат, который позволит нам создавать окно из popove`ра.


#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate, NSPopoverDelegate>


Связываем наше добавленное окно с оутлетом, по которому мы будем обращаться к этому окну.


@property (unsafe_unretained) IBOutlet NSWindow *popWindow;


Добавляем в .m - файл метод detachableWindowForPopover:


- (NSWindow *) detachableWindowForPopover:(NSPopover *)popover {
    
    return [self popWindow];
}


В принципе все. Должно работать. При появлении popove`ра перетаскиваем его в любое место и создается наше окошко.