У меня был вид, который содержал список лагерей, и касание одного приводит пользователей к деталям лагеря. Я хотел позволить пользователю проводить пальцем влево и вправо, чтобы перемещаться по списку лагерей с отображением деталей каждого из них. Мне нужна была визуальная анимация, которая показывала пролистывание и чтобы кнопка «Назад» всегда означала перемещение назад вверх по стеку навигации, то есть возврат к списку.
Первое представление с таблицей результатов - это единственный объект, который знает, что означают слова «предыдущий» и «следующий», поэтому они будут нести ответственность за реализацию действия смахивания. Поскольку мы будем вносить изменения в «отображаемую в данный момент» строку / раздел в этом представлении, в то время как представление не является текущим представлением, вам потребуется добавить переменные / свойства для отслеживания этого.
Поскольку навигационный контроллер уже может анимировать просмотр изменений, я позволил ему выполнить большую часть работы. При перемещении «предыдущий» я создаю новое представление с предыдущими сведениями о записи, вставляю его в стек навигации (между представлением LIST и текущим представлением DETAIL) и выполняю popViewControllerAnimated, который обеспечивает визуальный эффект, а также выгружает представление сведений. просто оживил.
Чтобы перейти к «следующим» сведениям о лагере, я создаю новое представление со следующими сведениями о записи, добавляю его в конец стека навигации, он оживляет, а затем очищаю стек навигации, удаляя представление сведений. это просто оживило.
Итак, в двух словах:
В подробном представлении будут обнаружены жесты смахивания и сообщен родитель.
Родитель определяет следующую / предыдущую строку, которая должна отображаться.
Родитель заменяет текущий вид новым, анимируя замену влево / вправо, чтобы визуально указать эффект. Родитель также обновляет стек навигации, чтобы кнопка «Назад» всегда действовала как всплывающее окно в представлении LIST, а не в ранее показанном представлении DETAIL.
Я не могу опубликовать весь свой код здесь, но ниже приведено большинство. Некоторые конкретные GOTCHAS, которые нужно остерегаться:
Управление стеком навигации может привести к ошибкам предупреждения во время выполнения, если вы попытаетесь удалить VC во время его анимации. Поэтому мы ждем, пока он не будет полностью заменен, затем удаляем его, зарегистрировавшись в качестве делегата для контроллера навигации и используя метод didShowViewController, чтобы определить, когда безопасно вносить изменения. Это усложнение необходимо только при движении вперед в списке, поскольку в логике «назад» контроллер навигации очищает себя после popViewController.
Чтобы использовать didShowViewController, вы должны установить делегата. Делегат не должен быть VC, который может уйти, иначе вы получите сбой. У меня только управляющее представление LIST, установленное в качестве делегата.
По мере того, как вы управляете строкой / разделом, информацию о которых просматривает пользователь, я также перемещаю выделение (и прокручиваю таблицу) в скрытом представлении LIST, чтобы, когда они в конечном итоге переходили к «BACK», отображается последний просмотренный элемент.
При создании представления DETAIL передайте родительский объект, определите методы, позволяющие родителю узнать, что произошло свайп, и зарегистрируйте распознаватели свайпов в представлении DETAIL в методе viewDidLoad,
Код в представлении LIST (родитель)
-(NSString *) nameOfPreviousCampAndUpdateCurrents;
{
// pseudo code
// targetsection = srcSection
// targetrow = srcRow-1.
// if targetrow < 0
// targetsection = srcSection - 1
// targetrow = last row of targetsection
// if targetSection < 0
// return nil;
//
// return name at targetsection, targetrow
NSInteger targetSection;
NSInteger targetRow;
NSString *results = nil;
targetSection = self.currentDetailViewSection;
targetRow = self.currentDetailViewRow-1;
if (targetRow < 0)
{
targetSection--;
if (targetSection <0)
{
return nil;
}// end if
NSInteger numberOfRowsInSection = [self tableView:self.myTable numberOfRowsInSection:targetSection];
targetRow = numberOfRowsInSection-1;
}// end if
results = [self getCampNameInSection:targetSection atOffset:targetRow];
self.currentDetailViewSection = targetSection;
self.currentDetailViewRow = targetRow;
return results;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
CampDetails *detailViewController = [[[CampDetails alloc] initWithNibName:nil bundle:nil] autorelease];
detailViewController.campName = [self getCampNameInSection:indexPath.section atOffset:indexPath.row];
detailViewController.campID = [self getCampIDForSection:indexPath.section atOffset:indexPath.row];
detailViewController.parent = self;
self.currentDetailViewSection = indexPath.section;
self.currentDetailViewRow = indexPath.row;
// ...
// Pass the selected object to the new view controller.
[[self navigationController] pushViewController:detailViewController animated:YES];
//[detailViewController release];
}
-(void) viewDidLoad;
{
// The ROOT view controller should do this.
if ([[self.navigationController viewControllers] count] == 1)
{
self.navigationController.delegate = self;
}// end if
}
-(void) moveToNextCamp;
{
NSString *nextCamp = [self nameOfNextCampAndUpdateCurrents];
if (nextCamp == nil)
{
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Warning"
message:@"You are already at the last item in the list of camps."
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
return;
}// end if
CampDetails *detailViewController = [[[CampDetails alloc] initWithNibName:nil bundle:nil]autorelease];
detailViewController.campName = nextCamp;
detailViewController.campID = [self getCampIDForSection:self.currentDetailViewSection atOffset:self.currentDetailViewRow];
detailViewController.parent = self;
// do the animation to the right
[self.navigationController pushViewController:detailViewController animated:YES];
// remove the previous controller so that popping the current one takes us "up"
// WHILE THE FOLLOWING CODE DOES WORK, it also results in a runtime warning.
// so instead, we tinker with the controller stack only when it's safe (see below)
// NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
// [viewControllers removeObjectAtIndex:1];
// [self.navigationController setViewControllers:viewControllers animated:NO];
//
// clean up the stack AFTER the child is shown.
self.userJustSwiped = YES;
[self updateTableHighlightAndScrollPosition];
}
-(void) moveToPreviousCamp;
{
NSString *previousCamp = [self nameOfPreviousCampAndUpdateCurrents];
if (previousCamp == nil)
{
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Warning"
message:@"You are already at the first item in the list of camps."
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
return;
}// end if
CampDetails *detailViewController = [[[CampDetails alloc] initWithNibName:nil bundle:nil]autorelease];
detailViewController.campName = previousCamp;
detailViewController.campID = [self getCampIDForSection:self.currentDetailViewSection atOffset:self.currentDetailViewRow];
detailViewController.parent = self;
// add the controller so that popping the current one takes us there
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
NSInteger lastNavStackEntryIndex = [viewControllers count]-1;
[viewControllers insertObject:detailViewController atIndex:lastNavStackEntryIndex];
[self.navigationController setViewControllers:viewControllers animated:NO];
// do the animation (which also releases the previously current vc)
[self.navigationController popViewControllerAnimated:YES];
[self updateTableHighlightAndScrollPosition];
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// IF we just swiped to a details view, do some clean up
if (self.userJustSwiped)
{
self.userJustSwiped = NO;
// clean up the stack AFTER the child is shown. remove the previous controller so that popping the current one takes us "up"
NSMutableArray *viewControllersArray = [NSMutableArray arrayWithArray:navigationController.viewControllers];
NSInteger lastNavStackEntryIndex = [viewControllersArray count] - 1;
[viewControllersArray removeObjectAtIndex:lastNavStackEntryIndex-1];
[navigationController setViewControllers:viewControllersArray animated:NO];
}// end if
}
-(void) userSwipedLeftOnChild;
{
[self moveToNextCamp];
}
-(void) userSwipedRightOnChild;
{
[self moveToPreviousCamp];
}
Код в представлении ДЕТАЛИ (дочерний):
-(void) leftSwipe:(UIGestureRecognizer*)recognizer;
{
[self.parent userSwipedLeftOnChild];
}
-(void) rightSwipe:(UIGestureRecognizer*)recognizer;
{
[self.parent userSwipedRightOnChild];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// add swipe recognizers
UISwipeGestureRecognizer *leftSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(leftSwipe:)];
[leftSwipe setDirection:UISwipeGestureRecognizerDirectionLeft];
[self.view addGestureRecognizer:leftSwipe];
[leftSwipe release];
UISwipeGestureRecognizer *rightSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(rightSwipe:) ];
[rightSwipe setDirection:UISwipeGestureRecognizerDirectionRight];
[self.view addGestureRecognizer:rightSwipe];
[rightSwipe release];
}