Мне нужен совет по пользовательскому интерфейсу.
У меня есть мнение, что необходимо загрузить данные в таком формате:
{
"heading": "This is a header",
"content": "This is some detailed content about the header"
},
{
"heading": "This is another headline.",
"content": " These are more details about the headline. "
}
Вот параметры: При загрузке он должен просто отображать заголовки в виде таблицы. Нажатие на заголовок расширит эту ячейку и загрузит содержимое или подробную информацию о ней. Вот примерный набросок:
В процессе:
- Заголовок 1
- Заголовок 2
- Заголовок 3
- Заголовок 4
При нажатии заголовка 2:
- Заголовок 1
- Заголовок 2
- Здесь отображается содержание заголовка 2.
- Заголовок 3
- Заголовок 4
Также должен быть элемент кнопки панели, который либо развернет, либо свернет все ячейки. Кому бы это понравилось:
Свернуть все:
- Заголовок 1
- Заголовок 2
- Заголовок 3
- Заголовок 4
Расширить все:
- Заголовок 1
- Здесь отображается содержание заголовка 1
- Заголовок 2
- Здесь отображается содержание заголовка 2.
- Заголовок 3
- Здесь отображается содержание заголовка 3.
- Заголовок 4
- Здесь отображается содержание заголовка 4.
Я использовал какую-то странную родительскую / дочернюю логику, чтобы заставить отдельные ячейки расширяться, но я думаю, что пошел по темному пути, потому что теперь я пытаюсь реализовать развернуть / свернуть все, и я застрял.
Кто-нибудь знает какой-либо открытый исходный код, который выполняет этот тип представления таблицы аккордеона и / или какие-либо предложения о том, как настроить контроллер представления для этого? Я видел, как несколько библиотек расширялись и сворачивались по отдельным ячейкам, но уметь делать все из них становится непросто.
2 ответа
Вот как я это делаю, возможно, чуть более простой, хотя определенно похожий подход :)
#import "ViewController.h"
//dont worry, the header is empty except for import <UIKit/UIKit.h>, this is a subclass on UIViewController
@interface ViewController ()<UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) UITableView *tableView;
@end
@implementation ViewController
{
//ivars
BOOL sectionIsOpen[4]; //we will use this BOOL array to keep track of the open/closed state for each section. Obviously I have the number of sections fixed at 4 here, but you could make a more dynamic array with malloc() if neccesary..
}
- (void)viewDidLoad {
[super viewDidLoad];
UITableView *tv = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
tv.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
tv.dataSource = self;
tv.delegate = self;
[self.view addSubview:tv];
self.tableView = tv;
// Do any additional setup after loading the view, typically from a nib.
}
#pragma mark - UITableViewDataSource
-(NSInteger )numberOfSectionsInTableView:(UITableView *)tableView{
return 4;
}
-(NSInteger )tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return ((sectionIsOpen[section]) ? [self numberOfRowsInSection:section] : 0);
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
//put your switch() here...
return [NSString stringWithFormat:@"I am section %i", (int)section ];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellId = @"cellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellId];
}
//etc etc decorate your cell...
cell.textLabel.text = [NSString stringWithFormat:@"cell %i / %i", (int)indexPath.section, (int)indexPath.row ];
return cell;
}
#pragma mark - UITableViewDelegate
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
const CGRect fr = CGRectMake(0, 0, 320.0, 40.0 );
UIButton *btn = [[UIButton alloc]initWithFrame:fr];
[btn setTitle:[self tableView:tableView titleForHeaderInSection:section] forState:UIControlStateNormal ];
[btn setTag:section];
[btn addTarget:self action:@selector(sectionOpenToggle:) forControlEvents:UIControlEventTouchUpInside];
// add an image, colour etc if you like
return btn;
}
#pragma mark - tableViewHelpers
//the number of rows in sectionX when it is open...
-(NSInteger )numberOfRowsInSection:(NSInteger )section{
return section + 1;
}
//opening/closing a section
-(void )setSection:(NSInteger )section toOpen:(BOOL )open{
if (open != sectionIsOpen[section]) {
//build an array of indexPath objects
NSMutableArray *indxPths = [NSMutableArray array];
for (NSInteger i = 0; i < [self numberOfRowsInSection:section]; i ++) {
[indxPths addObject: [NSIndexPath indexPathForRow:i inSection:section ]
];
}
[self.tableView beginUpdates];
if (open) {
[self.tableView insertRowsAtIndexPaths:indxPths withRowAnimation:UITableViewRowAnimationFade];
//nb there is a large ENUM of tableViewRowAnimation types to experiment with..
}else{
[self.tableView deleteRowsAtIndexPaths:indxPths withRowAnimation:UITableViewRowAnimationFade];
}
sectionIsOpen[section] = open;
[self.tableView endUpdates];
}
}
-(void )sectionOpenToggle:(id )sender{
[self setSection:[sender tag] toOpen: !sectionIsOpen[[sender tag]] ];
}
// open/close all sections.
-(void )setAllSectionsOpen:(BOOL )open{
for (NSInteger i = 0; i < [self numberOfSectionsInTableView:self.tableView]; i ++) {
[self setSection:i toOpen:open];
}
}
//these two for your convenience, hook up to navbar items etc..
-(IBAction)openAllSections:(id)sender{
[self setAllSectionsOpen:YES];
}
-(IBAction)closeAllSections:(id)sender{
[self setAllSectionsOpen:NO];
}
@end
Я не являюсь носителем английского языка, поэтому, если некоторые из моих объяснений недостаточно ясны, скажите мне, и я постараюсь их перефразировать.
В любом случае, могли бы быть лучшие способы сделать это, но это было только в моей голове,
Приведенный ниже код может показаться пугающим и сложным, но я лично считаю, что это действительно
прямолинейно,
Я только что добавил много комментариев, объясняющих каждый шаг, поэтому код на самом деле не такой
длинный / сложный / беспорядочный, как может показаться на первый взгляд.
Из приведенного выше примера мне кажется, что ваш источник данных - это NSArray
, который содержит NSDictionary
объектов,
И что каждый NSDictionary
содержит один заголовок (раздел) и только одно содержимое (строку) для этого раздела,
Таким образом, приведенный ниже пример настроен для обработки только этого - представления таблицы с несколькими разделами и одной строкой в каждом разделе,
Каким его источником данных является NSArray
, который содержит NSDictionary
объектов.
Поскольку я не знаю, является ли ваш текущий источник данных изменяемым или нет, в моем примере я сначала создам его изменяемую копию и буду использовать ее во всем коде.
На протяжении всего кода я буду предполагать, что self.tableView
- это ваше табличное представление, а self.dataArray
- это массив словарей, которые вы разместили выше,
Я также предполагаю, что вы уже установили табличное представление delegate
и dataSource
как self
, либо в коде, либо в раскадровке.
// Here we define the height of each header,
// I've arbitrarily chosen 50 points. You can change it as you like.
// The reason I've declare it like this, is that I'm using its
// height to also create a UIView for the header, so this way
// if you want to change the height, you need to only change it once.
#define kHeaderHeight 50
-(void)viewDidLoad {
...
...
// The method below will be called to create an mutable copies of the
// dictionaries in your data source, plus add them another object
// which will indicate in our code if the correspond header
// should be expanded or collapsed
[self createDataSource];
// The below line is not mandatory, but I personally like to add it
// So collapsed sections won't have the row's 'bounds' under them.
self.tableView.tableFooterView = [[UIView alloc] initWithFrame: CGRectZero];
...
...
}
-(void)createDataSource {
// Here we are basically going to create a temporary mutable array,
// then we are going to iterate through self.dataArray array,
// Make a mutable copy of every dictionary in it, Add a BOOL value
// it that indicates if the row is expanded or not, add the new mutable
// dictionary to the temporary array, and then make self.dataArray
// point to an immutable copy of the new array we've created
NSMutableArray *array = [[NSMutableArray alloc] init];
for(int i = 0; i < [self.dataArray count]; i++) {
NSMutableDictionary *dict = [self.dataArray[i] mutableCopy];
[dict setObject:@(NO) forKey:@"expanded"];
[array addObject:dict];
}
self.dataArray = [array copy];
}
// Now we will set the height of each header.
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return kHeaderHeight;
}
// Here we create a custom view for the header, so we can make its title clickable,
// That will expand/collapse each section
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
// Here we are creating a view for the header, and use our defined header
// height to set its height appropriately.
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kHeaderHeight)];
// Then create a button
// I've arbitrarily chosen a size of 100x20 and created a frame to be placed in the
// middle of the above header
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(header.frame.size.width/2 - 50, header.frame.size.height/2 - 10, 100, 20)];
NSString *headerTitle = [self.dataArray[section] objectForKey:@"heading"];
[button setTitle:headerTitle forState:UIControlStateNormal];
// We set the button tag to correspond to its section for future use
button.tag = section;
// I've arbitrarily chose to set the button colour to gray colour
button.titleLabel.textColor = [UIColor grayColor];
// Then we need to actually add an action to the button
[button addTarget:self action:@selector(updateTableView:) forControlEvents:UIControlEventTouchUpInside];
// Then we need to add the button to the header view itself
[header addSubview:button];
return header;
}
// Here we are just setting the number of sections to correspond to the
// number of items we have in our data array
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [self.dataArray count];
}
// Here we are finally using the BOOl value we've added to our dictionary at the
// beginning of our code. If the "expanded" BOOL value is YES, return 1 row,
// else, the section is collapsed, so return 0 rows
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if([[self.dataArray[section] objectForKey:@"expanded"] boolValue]) {
return 1;
} else {
return 0;
}
}
// Here is just a simple method to create the cells that will use our dataArray
// as their source for their title
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *identifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
cell.textLabel.text = [self.dataArray[indexPath.row] objectForKey:@"content"];
return cell;
}
// This method being called when clicking on the table view's headers
// it uses the button tag we've set earlier, to determine which section we're trying
// to collape/expand, it retrieves the current "expanded" bool value, and then store
// the opposite value back in our dictionary (so if table was collapsed, meaning
// its "expanded" value is NO, after this will run, it will be "YES", which
// will be evaluated after our next line, which tells the table view to reload
// this specific section).
// NOTE that while our dataArray is immutable array, meaning we can't modify it,
// it points to an mutable dictionaries, so we have no problem modifying them.
-(void)updateTableView:(UIButton *)sender {
BOOL expanded = [[self.dataArray[sender.tag] objectForKey:@"expanded"] boolValue];
[self.dataArray[sender.tag] setObject:@(!expanded) forKey:@"expanded"];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:sender.tag] withRowAnimation:UITableViewRowAnimationFade];
}
// Connect the below methods to your collapse/expand all buttons.
// What the below method actually does, is iterating thru all of the dictionaries
// in dataArray, changes their value to be corresponding to what we are trying to
// do (expand or collapse all) and then calling reloadSections:withRowAnimation:
// on our table view.
// You might wonder why I just don't call reloadData.
// Although it will do the job, I like the following more, because I personally
// feel it gives more 'smooth' update of the UI
-(void)expandAll:(UIButton *)sender {
for(int i = 0; i < [self.dataArray count]; i++) {
[self.dataArray[i] setObject:@(YES) forKey:@"expanded"];
}
[self.tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.dataArray.count)] withRowAnimation:UITableViewRowAnimationFade];
}
-(void)collapseAll:(UIButton *)sender {
for(int i = 0; i < [self.dataArray count]; i++) {
[self.dataArray[i] setObject:@(NO) forKey:@"expanded"];
}
[self.tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.dataArray.count)] withRowAnimation:UITableViewRowAnimationFade];
}
Удачи, дружище.
Похожие вопросы
Связанные вопросы
Новые вопросы
ios
iOS - мобильная операционная система, работающая на Apple iPhone, iPod touch и iPad. Используйте этот тег [ios] для вопросов, связанных с программированием на платформе iOS. Используйте связанные теги [target-c] и [swift] для проблем, характерных для этих языков программирования.