Пользовательские макеты: обработанный пример

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

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

Поскольку эта глава представляет реализацию пользовательского макета в определенном порядке, следуйте вдоль примера сверху донизу с определенной целью реализации в памяти. Эта глава фокусируется на создании пользовательского макета, не на реализации законченного приложения. Поэтому реализации представлений и контроллеров, используемых для создания конечного продукта, не предоставлены. Расположение использует пользовательские ячейки представления набора в качестве своих ячеек и пользовательского представления для создания строк, подключающих ячейки к друг другу. Создание пользовательских ячеек и представлений для представлений набора, а также требований для использования представления набора, охвачено в предыдущих главах. Для рассмотрения этой информации посмотрите Основы Представления Набора и Разработку Источника данных и Делегата.

Понятие

Цель этого обработанного примера состоит в том, чтобы реализовать пользовательский макет для отображения hierarchichal дерева информации, такой как схема, замеченная на рисунке 6-1. Пример обеспечивает отрывки кода, сопровождаемого объяснением кода, вместе с точкой в процессе настройки, которого Вы достигли. Каждый раздел представления набора составляет один уровень глубины в дерево: Раздел 0 содержит только ячейку NSObject. Раздел 1 содержит всю дочернюю ячейку NSObject. Раздел 2 содержит все дочерние ячейки тех дочерних элементов и т.д. Каждая из ячеек является пользовательской ячейкой с меткой для связанного имени класса, и соединения между ячейками являются дополнительными представлениями. Поскольку класс представления коннектора должен определить, сколько соединений для рисования ему нужен доступ к данным в нашем источнике данных. Это поэтому целесообразно реализовывать эти соединения как дополнительные представления и не представления художественного оформления.

  Иерархия классов рисунка 6-1

Инициализация

Первый шаг в создании пользовательского макета должен разделить на подклассы UICollectionViewLayout класс. Выполнение так предоставляет Вам основы, необходимые для создания пользовательского макета.

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

Перечисление 6-1 показывает необходимый код для заголовочного файла пользовательского макета. Теперь, любой класс, реализующий протокол MyCustomProtocol, может использовать пользовательский макет, и расположение может запросить тот класс для получения информации, в которой это нуждается.

Перечисление 6-1  , Соединяющееся с пользовательским протоколом

@interface MyCustomLayout : UICollectionViewLayout
@property (nonatomic, weak) id<MyCustomProtocol> customDataSource;
@end

Затем, потому что число элементов, которыми будет управлять представление набора, является относительно низким, пользовательский макет использует кэширующуюся систему для хранения атрибутов макета, которые это генерирует при подготовке расположения и затем получает эти сохраненные значения каждый раз, когда представление набора просит их. Перечисление 6-2 показывает эти три частных собственности, которые наше расположение должно будет поддержать и init метод. layoutInformation словарь содержит все атрибуты макета для всех типов представлений в нашем представлении набора, и maxNumRows свойство отслеживает то, сколько строк необходимо для заполнения самого высокого столбца нашего дерева. insets возразите интервалу средств управления между ячейками, и используется в установке кадров для представлений и размера содержания. Значения для первых двух свойств установлены при подготовке расположения, но insets объект должен быть установлен с помощью init метод. В этом случае, INSET_TOP, INSET_LEFT, INSET_BOTTOM, и INSET_RIGHT обратитесь к константам, которые Вы определяете для каждого параметра.

Перечисление 6-2  , Инициализирующее переменные

@interface MyCustomLayout()
 
@property (nonatomic) NSDictionary *layoutInformation;
@property (nonatomic) NSInteger maxNumRows;
@property (nonatomic) UIEdgeInsets insets;
 
@end
 
-(id)init {
    if(self = [super init]) {
        self.insets = UIEdgeInsetsMake(INSET_TOP, INSET_LEFT, INSET_BOTTOM, INSET_RIGHT);
    }
    return self;
}

Последний шаг для этого пользовательского макета должен создать атрибуты пользовательского макета. Несмотря на то, что этот шаг не всегда необходим, в этом случае, когда ячейки помещаются, коду нужен доступ к индексным путям дочерних элементов текущей ячейки так, чтобы это могло скорректировать дочерние кадры ячейки для соответствия того из их родителя. Так разделение на подклассы UICollectionViewLayoutAttributes сохранить массив дочерних элементов ячейки предоставляет ту информацию. Вы разделяете на подклассы UICollectionViewLayoutAttributes, и в заголовочном файле добавляют следующий код:

@property (nonatomic) NSArray *children;

Как объяснено в UICollectionViewLayoutAttributes ссылка класса, разделение на подклассы атрибутов макета требует, чтобы Вы переопределили наследованный isEqual: метод в iOS 7 и позже. Для получения дополнительной информации о том, почему это, посмотрите Ссылку класса UICollectionViewLayoutAttributes.

Реализация для isEqual: метод в этом случае прост, потому что существует только одно поле для сравнения — содержание дочернего массива. Если массивы обоих атрибутов макета возражают соответствию, то они должны быть равными, потому что дочерние элементы могут принадлежать только одному классу. Перечисление 6-3 показывает реализацию для isEqual: метод.

Перечисление 6-3  , Выполняющее требования для разделения на подклассы атрибутов макета

-(BOOL)isEqual:(id)object {
    MyCustomAttributes *otherAttributes = (MyCustomAttributes *)object;
    if ([self.children isEqualToArray:otherAttributes.children]) {
        return [super isEqual:object];
    }
    return NO;
}

Не забудьте включать заголовочный файл для атрибутов пользовательского макета в файле пользовательского макета.

В этой точке в процессе, Вы готовы начать реализовывать основную часть пользовательского макета с началом, которое Вы положили.

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

Теперь, когда все необходимые компоненты были инициализированы, можно подготовить расположение. Представление набора сначала вызывает prepareLayout метод во время процесса создания макета. В этом примере, prepareLayout метод используется, чтобы инстанцировать всех объектов атрибутов макета для каждого представления в представлении набора и затем кэшировать те атрибуты в нашем layoutInformation словарь для более позднего использования. Для получения дополнительной информации о prepareLayout метод, посмотрите Подготовку Расположения.

Создание атрибутов макета

Реализация в качестве примера prepareLayout метод разделяется на две части. Рисунок 6-2 показывает цель для первой половины метода. Код выполняет итерации по каждой ячейке, и если та ячейка имеет дочерние элементы, связывает те дочерние элементы с родительской ячейкой. Как Вы видите в числе, этот процесс сделан для каждой ячейки, включая дочерние ячейки других родительских ячеек.

  Соединительный родитель рисунка 6-2 и дочерний элемент индексируют пути

Перечисление 6-4 показывает первую половину prepareLayout реализация метода. Оба непостоянных словаря, инициализированные в начале кода, формируют основание механизма кэширования. Первое, layoutInformation, локальный эквивалент layoutInformation свойство. Создание локальной непостоянной копии позволяет переменной экземпляра быть неизменной, который целесообразен в реализации пользовательского макета, потому что атрибуты макета не должны быть изменены после prepareLayout метод заканчивает работать. Код тогда выполняет итерации по каждому разделу в увеличивающемся порядке и затем по каждому элементу в каждом разделе для создания атрибутов для каждой ячейки. Пользовательский метод attributesWithChildrenForIndexPath: возвращает экземпляр атрибутов пользовательского макета, с children свойство заполняется с индексными путями дочерних элементов для элемента в текущем индексном пути. Объект атрибутов тогда хранится в локальной переменной cellInformation словарь с его индексом соединяет каналом как ключ. Эта начальная буква передает по всем элементам, позволяет коду устанавливать дочерние элементы для каждого элемента прежде, чем установить кадр элемента.

Перечисление 6-4  , Создающее атрибуты макета

- (void)prepareLayout {
    NSMutableDictionary *layoutInformation = [NSMutableDictionary dictionary];
    NSMutableDictionary *cellInformation = [NSMutableDictionary dictionary];
    NSIndexPath *indexPath;
    NSInteger numSections = [self.collectionView numberOfSections;]
    for(NSInteger section = 0; section < numSections; section++){
        NSInteger numItems = [self.collectionView numberOfItemsInSection:section];
        for(NSInteger item = 0; item < numItems; item++){
            indexPath = [NSIndexPath indexPathForItem:item inSection:section];
            MyCustomAttributes *attributes =
            [self attributesWithChildrenAtIndexPath:indexPath];
            [cellInformation setObject:attributes forKey:indexPath];
        }
    }
    //end of first section

Хранение атрибутов макета

Рисунок 6-3 изображает процесс, происходящий во второй половине prepareLayout метод, в котором древовидная иерархия создается от последней строки до первого. Этот подход может сначала казаться странным, но это - умный способ устранить сложность, связанную с корректирующимися дочерними кадрами ячейки. Поскольку кадры дочерних ячеек должны совпасть с теми из их родителя, и потому что сумма пространства между ячейками на основе от строки к строке зависит от того, сколько дочерних элементов ячейка имеет (включая то, сколько дочерних элементов каждая дочерняя ячейка имеет и т.д.), Вы хотите установить кадр дочернего элемента прежде, чем установить родителя. Таким образом дочерняя ячейка и все ее дочерние ячейки могут быть скорректированы для соответствия ячейки их полного родителя.

На шаге 1 ячейки последнего столбца были помещены в последовательный порядок. На шаге 2 расположение определяет кадры для второго столбца. В этом столбце ячейки могут быть размечены последовательно, так как никакая ячейка не имеет больше чем один дочерний элемент. Однако кадр зеленой ячейки должен быть скорректирован для соответствия той из его родительской ячейки, таким образом, это смещается вниз одно пространство. В последнем шаге помещаются ячейки для первого столбца. Первые три ячейки второго столбца являются дочерними элементами первой ячейки в первом столбце, таким образом, ячейка после первой ячейки в первом столбце смещается вниз. В этом случае не фактически необходимо сделать так, так как две ячейки после первого не имеют никаких собственных дочерних элементов, но объект расположения не достаточно умен для знания этого. Скорее это всегда корректирует пространство в случае, если любая ячейка после одной с дочерними элементами имеет собственные дочерние элементы. Также, зеленые ячейки теперь оба сместились вниз для соответствия того из их родителя.

Рисунок 6-3  процесс структурирования

Перечисление 6-5 показывает вторую половину prepareLayout метод, в котором установлены кадры для каждого элемента. Прокомментированные числа после некоторых строк кода соответствуют пронумерованным объяснениям, найденным после кода.

Перечисление 6-5  , Хранящее атрибуты макета

    //continuation of prepareLayout implementation
    for(NSInteger section = numSections - 1; section >= 0; section—-){
        NSInteger numItems = [self.collectionView numberOfItemsInSection:section];
        NSInteger totalHeight = 0;
        for(NSInteger item = 0; item < numItems; item++){
            indexPath = [NSIndexPath indexPathForItem:item inSection:section];
            MyCustomAttributes *attributes = [cellInfo objectForKey:indexPath]; // 1
            attributes.frame = [self frameForCellAtIndexPath:indexPath
                                withHeight:totalHeight];
            [self adjustFramesOfChildrenAndConnectorsForClassAtIndexPath:indexPath]; // 2
            cellInfo[indexPath] = attributes;
            totalHeight += [self.customDataSource
                            numRowsForClassAndChildrenAtIndexPath:indexPath]; // 3
        }
        if(section == 0){
            self.maxNumRows = totalHeight; // 4
        }
    }
    [layoutInformation setObject:cellInformation forKey:@"MyCellKind"]; // 5
    self.layoutInformation = layoutInformation
}

В Перечислении 6-5 код пересекает разделы в порядке убывания, создавая дерево от спины до передней стороны. totalHeight переменная отслеживает, каким количеством строк вниз текущий элемент должен быть. Эта реализация не отслеживает интервал энергично, просто оставляя вакуум ниже ячеек с дочерними элементами так, чтобы дочерние элементы двух ячеек никогда не накладывались, и totalHeight переменная помогает выполнить это. Код выполняет это в следующем порядке:

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

  2. Пользовательское adjustFramesOfChildrenAndConnectorsForClassAtIndexPath: метод рекурсивно корректирует кадры дочерних элементов и внуков всей ячейки и т.д. для соответствия кадра ячейки.

  3. После откладывания скорректированных атрибутов в словаре, totalHeight переменная корректируется для отражения, где кадр следующего элемента должен быть. Это - то, где код использует в своих интересах пользовательский протокол. Безотносительно реализаций объекта тот протокол должен реализовать numRowsForClassAndChildrenAtIndexPath: метод, возвращающийся, сколько строки каждый класс должны занять данный, сколько дочерних элементов это имеет.

  4. maxNumRows свойство (позже должен был установить размер содержания) установлено разделить общую высоту 0. Столбец с самой долгой высотой всегда является разделом 0, которому привели высоту в соответствие для всех дочерних элементов в дереве, потому что эта реализация не включает умную корректировку пространства.

  5. Метод завершает путем вставки словаря со всеми атрибутами ячеек в локальную переменную layoutInformation словарь с уникальным идентификатором строки как его ключ.

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

Обеспечение размера содержания

В подготовке расположения, кодовые наборы значение maxNumRows быть числом строк в самом большом разделе в расположении. Эта информация может быть эффективно использована для установки надлежащего размера содержания, который является следующим шагом в процессе создания макета. Перечисление 6-6 показывает реализацию для collectionViewContentSize. Это полагается на константы ITEM_WIDTH и ITEM_HEIGHT, которые являются по-видимому глобальной переменной к приложению (например, они необходимы в пользовательской реализации ячейки для калибровки метки ячейки правильно).

Перечисление 6-6  , Измеряющее предметную область

- (CGSize)collectionViewContentSize {
    CGFloat width = self.collectionView.numberOfSections * (ITEM_WIDTH + self.insets.left + self.insets.right);
    CGFloat height = self.maxNumRows * (ITEM_HEIGHT + _insets.top + _insets.bottom);
    return CGSizeMake(width, height);
}

Обеспечение атрибутов макета

Со всеми объектами атрибутов макета, инициализированными и кэшируемыми, код полностью подготовлен обеспечить всю информацию макета, которую требуют в layoutAttributesForElementsInRect: метод. Этот метод является вторым шагом в процессе создания макета, и в отличие от этого prepareLayout метод, это требуется. Метод обеспечивает прямоугольник и ожидает массив объектов атрибутов макета для любых представлений, содержавших в предоставленном прямоугольнике. В некоторых случаях представления набора, содержащие тысячи элементов, могли бы ожидать, пока этот метод не вызывают для инициализации объектов атрибутов макета для только элементов, содержавших в предоставленном прямоугольнике, но эта реализация полагается на кэширование вместо этого. Поэтому layoutAttributesForElementsInRect: метод просто требует цикличного выполнения по всем сохраненным атрибутам, и сбор их в единый массив возвратился к вызывающей стороне.

Перечисление 6-7 показывает реализацию layoutAttributesForElementsInRect: метод. Код пересекает все подсловари, содержащие объекты атрибутов макета для определенных типов представлений в основном словаре _layoutInformation. Если атрибуты, исследованные в подсловаре, содержатся в данном прямоугольнике, они добавляются к массиву, хранящему все атрибуты в том прямоугольнике, возвращающемся после того, как все сохраненные атрибуты были проверены.

  Сбор перечисления 6-7 и обработка сохраненных атрибутов

- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
    NSMutableArray *myAttributes [NSMutableArray arrayWithCapacity:self.layoutInformation.count];
    for(NSString *key in self.layoutInformation){
        NSDictionary *attributesDict = [self.layoutInformation objectForKey:key];
        for(NSIndexPath *key in attributesDict){
            UICollectionViewLayoutAttributes *attributes =
            [attributesDict objectForKey:key];
            if(CGRectIntersectsRect(rect, attributes.frame)){
                [attributes addObject:attributes];
            }
        }
    }
    return myAttributes;
}

Обеспечение отдельных атрибутов, когда требуется

Как обсуждено в разделе Providing Layout Attributes On Demand, объект расположения должен быть подготовлен возвратить атрибуты макета для любого исключительного элемента любого вида представления в Вашем представлении набора в любое время, как только процесс создания макета завершен. Существуют методы для всех трех видов представлений — ячеек, дополнительных представлений и представлений художественного оформления — но приложение в настоящее время использует ячейки исключительно, таким образом, единственный метод, требующий реализации в настоящее время, layoutAttributesForItemAtIndexPath: метод.

Перечисление 6-8 показывает реализацию для этого метода. Это наслаждается сохраненный словарь для ячеек, и в том подсловаре, это возвращает объект атрибутов, хранивший с указанным индексным путем как его ключ.

Перечисление 6-8  , Обеспечивающее атрибуты для конкретных изделий

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    return self.layoutInfo[@"MyCellKind"][indexPath];
}

Рисунок 6-4 показывает то, на что расположение похоже в этой точке в коде. Все ячейки были помещены и скорректированы правильно для соответствия их родителей, но линии, соединяющие их еще, не были проведены.

Рисунок 6-4  расположение до сих пор

Слияние дополнительных представлений

В его текущем состоянии, отображения приложения все его ячейки правильно в иерархическом смысле, но потому что нет никаких строк, подключающих родителей к их дочерним элементам, диаграмму классов трудно интерпретировать. Для рисования линий, подключающих ячейки класса к их дочерним элементам, это внедрение приложения полагается на пользовательское представление, которое может быть включено в расположение как дополнительное представление. Для получения дополнительной информации о разработке дополнительных представлений посмотрите Подъемное Содержание Посредством Дополнительных Представлений.

Перечисление 6-9 показывает строки кода, который мог быть включен в реализацию prepareLayout включать дополнительные представления. Незначительные различия между созданием объектов атрибутов для ячеек и для дополнительных представлений - то, что метод для дополнительных представлений требует, чтобы идентификатор строки сказал, для какого дополнительного представления объект атрибутов. Это вызвано тем, что пользовательский макет может иметь многократные различные типы дополнительных представлений, тогда как каждое расположение может иметь только один тип ячейки.

Перечисление 6-9  , Создающее атрибуты, возражает для дополнительных представлений

// create another dictionary to specifically house the attributes for the supplementary view
NSMutableDictionary *supplementaryInfo = [NSMutableDictionary dictionary];
// within the initial pass over the data, create a set of attributes for the supplementary views as well
UICollectionViewLayoutAttributes *supplementaryAttributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:@"ConnectionViewKind" withIndexPath:indexPath];
[supplementaryInfo setObject: supplementaryAttributes forKey:indexPath];
// in the second pass over the data, set the frame for the supplementary views just as you did for the cells
UICollectionViewLayoutAttributes *supplementaryAttributes = [supplementaryInfo objectForKey:indexPath];
supplementaryAttributes.frame = [self frameForSupplementaryViewOfKind:@"ConnectionViewKind" AtIndexPath:indexPath];
[supplementaryInfo setObject:supplementaryAttributes ForKey:indexPath];
...
// before setting the instance version of _layoutInformation, insert the local supplementaryInfo dictionary into the local layoutInformation dictionary
[layoutInformation setObject:supplementaryInfo forKey:@"ConnectionViewKind"];

Поскольку код для дополнительных представлений подобен этому для ячеек, включая этот код в prepareLayout метод прост. Код использует тот же механизм кэширования для дополнительных представлений, как это делает для ячеек, с помощью другого словаря в частности для ConnectionViewKind дополнительное представление. Если бы Вы собирались добавить больше чем один вид дополнительного представления, то Вы создали бы другой словарь для такого представления и добавили бы эти строки кода для такого представления, также. Но в этом случае, расположение требует только одного вида дополнительного представления. Как в коде для инициализации атрибутов макета ячейки, эта реализация использует пользовательское frameForSupplementaryViewOfKind:AtIndexPath: метод для определения кадра дополнительного представления на основе того, какое представление это. Помните что пользовательское adjustFramesOfChildrenAndConnectorsForClassAtIndexPath: показанный в реализации prepareLayout метод должен включить корректировку любых дополнительных представлений, относящихся к расположению иерархии классов.

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

Наконец, как имел место для ячеек, представление набора может запросить дополнительные атрибуты представления на определенные представления в любое время. Также, реализация layoutAttributesForSupplementaryElementOfKind:atIndexPath: требуется.

Перечисление 6-10 показывает реализацию для метода, который почти идентичен тому из layoutAttributesForItemAtIndexPath:. Как исключение, с помощью предоставленного kind строка, а не жесткое кодирование тип представления в возвращаемое значение позволяет Вам использовать многократные дополнительные представления в своем пользовательском макете.

Перечисление 6-10  , Обеспечивающее дополнительное представление, приписывает по требованию

- (UICollectionViewLayoutAttributes *) layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    return self.layoutInfo[kind][indexPath];
}

Резюме

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