I have a viewController that displays the result of a query using a tableview). By taping on a row a I push a the childView and I set a navigationBar that contains 2 buttons on the right (Previous/Next). My question is : How can I switch to the previous or next "childView" when I tap on previous or next button? I would like to have also a transition effect while the view is switching? Any help?
3 回答
我有一个包含营地列表的视图,触摸一个视图会将用户带到营地详细信息。我想让用户左右滑动浏览营地列表,显示每个营地的详细信息。我想要一个显示滑动的视觉动画,并且“返回”按钮总是指向后移动导航堆栈,即返回到列表。
具有结果表的第一个视图是唯一知道“上一个”和“下一个”含义的对象,因此它们将负责实现滑动操作。因为我们将更改该视图中的“当前显示”行/部分,而该视图不是当前视图,所以您需要添加变量/属性来跟踪它。
因为导航控制器已经可以动画视图更改,所以我让它完成大部分工作。当移动“previous”时,我创建了一个包含先前条目详细信息的新视图,将其插入导航堆栈(在 LIST 视图和当前 DETAIL 视图之间),并执行 popViewControllerAnimated 提供视觉效果并卸载详细信息视图只是动画关闭。
为了移动到“下一个”营地详细信息,我创建了一个包含下一个条目详细信息的新视图,将其添加到导航堆栈的末尾,它会动画化,然后我通过删除刚刚动画化的详细信息视图来清理导航堆栈离开。
因此,简而言之:详细信息视图将检测滑动手势并通知父级。父级确定应显示的下一行/上一行。父级将当前视图替换为新视图,为替换左/右设置动画以直观地指示效果。父级还更新导航堆栈以确保“返回”按钮始终充当 LIST 视图的弹出窗口,而不是之前显示的 DETAIL 视图。
我不能在这里发布我所有的代码,但下面是大多数。需要注意的一些特定问题:
如果您尝试在 VC 制作动画时删除它,则操作导航堆栈可能会在运行时导致警告错误。因此,我们等到它被完全替换,然后通过注册为导航控制器的委托来移除它,并使用“didShowViewController”方法来检测何时可以安全地进行更改。只有在列表中向前移动时才需要这种复杂性,因为在“后退”逻辑中,导航控制器会在 popViewController 之后自行清理。
为了使用 didShowViewController 你必须设置一个委托。委托人不能是可能会消失的 VC,否则你会崩溃。我只有控制 LIST 视图将自己设置为委托。
当您操作用户正在查看哪个行/部分的详细信息时,我还会在隐藏的 LIST 视图上移动突出显示(并滚动表格),这样当他们最终“返回”到它时,会显示最后查看的项目.
创建 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];
}
DETAILS 视图中的代码(子):
-(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];
}
点击这些按钮时,您可以轻松地推送和弹出带有动画的视图控制器。
如果您需要帮助,请告诉我。
使用自动具有后退按钮的 UINavigationController,然后只需在右侧添加下一个按钮。
或者只是隐藏导航控制器的栏并添加您自己的两个按钮(我认为您当前的方法)。
在导航控制器上推送/弹出视图控制器:
推进下一个vc:
[myNavController pushViewController:vc animated:YES];
弹回最后一个vc:
myNavController popViewControllerAnimated:YES];
弹回堆栈上的第 3 个 vc:
[myNavController popToViewController:[myNavController.viewControllers objectAtIndex:2] animated:YES];
请注意,如果您想最小化内存使用量,请使用您自己的导航栏和您自己的按钮,并通过使用您自己在虚拟堆栈中的编码索引/计数来确保您只有两个视图控制器在运行。