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-переменные добавить таким образом нельзя), называется категорией.
Категория имеет свое имя, список методов и имя класса, который она расширяет. Описание категории имеет следующий вид:
Реализация категории выглядит следующим образом:
Ограничения при создании категорий:
Механизм, позволяющий расширять уже существующие классы (путем добавления новых методов, новые instance-переменные добавить таким образом нельзя), называется категорией.
Категория имеет свое имя, список методов и имя класса, который она расширяет. Описание категории имеет следующий вид:
#import "ClassName.h"
@interface ClassName (CategoryName)
// объявление методов
@end
#import "CategoryName.h"
@implementation ClassName (CategoryName)
// реализация методов
@end
- Невозможность добавления переменных;
- Возможная коллизия имен с самим классом, поэтому необходимо использовать оригинальные префиксы в наименовании своих методов.
Пример.
Расширим класс NSString, добавим метод который будет проверять является ли файл аудио-файлом. Создадим проект Foundation и добавим в проект новый класс-категорию NSStringAudioExtension (наследуемый от NSString):
Объявим в NSStringAudioExtension.h метод - (BOOL) isAudioFile:
В NSStringAudioExtension.m реализуем метод:
Дальше возвращаемся в main.m (технически класс NSString должен подхватить его расширение):
Пример.
Расширим класс 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, результат выполнения:
27 апр. 2014 г.
View-based NSTableView на основе ячеек из NSView (Злосчастная кнопка). Продолжение
В предыдущей статье о View-based NSTableView на основе ячеек из NSView не было реализации по использованию кнопки на виде (замечание в комментарии читателя). Порывшись в поиске (вместе с несуразными лексическими словосочетаниями), хоть какого-то решения так и не было найдено. Было решено использовать делегат и дать право AppDelegate кликать на эту злосчастную кнопку.
Все бы ничего, но как узнать индекс этой чертовой вьюхи, на которой находится эта кнопка? Подумав, поломав голову вместе с мозгами, решил по нажатию кнопки AppDelegate-м передавать ему эту вьюху. Ведь все они разные, т.к. под каждую строку выделена память, естественно и адреса их будут разные.
Создаем делегат - добавляем в проект новый protocol-файл. Объявим в нем метод который будет передавать право тыканья этой кнопки AppDelegate-у.
#import <Foundation/Foundation.h>
@protocol ButtonControllerProtocol <NSObject>
@required
- (void) viewButtonClicked: (NSButton *) clickedButton view: (NSView *) viewCell;
@end
Далее в ViewController.h добавим экшен на кнопку, подключим AppDelegate и укажем какой метод использовать при нажатии этой кнопки.
- (IBAction)buttonInfo:(id)sender {
AppDelegate *myAppDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
[myAppDelegate viewButtonClicked:sender view:[self view]];
}
В AppDelegate.h подключим созданный протокол и пропишем делегат:
#import "ButtonControllerProtocol.h"
@interface AppDelegate : NSObject <NSApplicationDelegate, NSTableViewDelegate, NSTableViewDataSource, ButtonControllerProtocol> {
Реализуем протокольный метод:
- (void)viewButtonClicked: (NSButton *) clickedButton view: (NSView *) viewCell {
NSLog (@"click");
}
Все бы хорошо, метод обрабатывается, выдает определенный результат, а дальше-то что? А дальше нам якобы нужно как-то менять данные на вьюхе. Вот здесь-то и пришлось помучатся. Хотя все оказалось немного проще. Мы помним что окно как главный вид, состоит из подвидов разной сложности, т.е. проще говоря на нашей вьюхе-подвиде тоже есть подвиды (subviews) - nsimageview, nstextfield и т.д. Остается лишь получить к ним доступ. Получим подвиды на вьхе-строке:
Загоним эти объекты в массив и потом создадим указатели на эти объекты и будем обращаться к непосредственно новым объектам-подвидам:
- (void)viewButtonClicked: (NSButton *) clickedButton view: (NSView *) viewCell {
// Получаем строку-вид в нашей таблице
NSView *clickedView = [clickedButton superview];
// Получаем список подвидов в строке-виде
NSArray *viewObjectsArray = [NSArray arrayWithArray:[clickedView subviews]];
// Коннектимся к подвиду и задаем значение
NSImageView *viewObjectImage = [viewObjectsArray objectAtIndex:0];
[viewObjectImage setImage:[NSImage imageNamed:@"Slice 1"]];
NSTextField *viewObjectFirstTextField = [viewObjectsArray objectAtIndex:1];
[viewObjectFirstTextField setStringValue:@"Slice 1"];
}
Вот такое вот нетривиальное, не претендующее ни на что, решение. Если есть более человеческое решение - прошу указать в комментариях.