ฉันจำเป็นต้องดำเนินการบางอย่างเมื่อกดปุ่มย้อนกลับ (กลับไปที่หน้าจอก่อนหน้ากลับไปที่มุมมองผู้ปกครอง) บน Navbar
มีวิธีการใดบ้างที่ฉันสามารถใช้เพื่อจับเหตุการณ์และปิดการทำงานบางอย่างเพื่อหยุดชั่วคราวและบันทึกข้อมูลก่อนที่หน้าจอจะหายไป
ฉันจำเป็นต้องดำเนินการบางอย่างเมื่อกดปุ่มย้อนกลับ (กลับไปที่หน้าจอก่อนหน้ากลับไปที่มุมมองผู้ปกครอง) บน Navbar
มีวิธีการใดบ้างที่ฉันสามารถใช้เพื่อจับเหตุการณ์และปิดการทำงานบางอย่างเพื่อหยุดชั่วคราวและบันทึกข้อมูลก่อนที่หน้าจอจะหายไป
คำตอบ:
อัปเดต:ตามความคิดเห็นบางส่วนการแก้ปัญหาในคำตอบเดิมดูเหมือนจะไม่ทำงานภายใต้สถานการณ์บางอย่างใน iOS 8+ ฉันไม่สามารถตรวจสอบได้ว่าเป็นเช่นนั้นจริงหากไม่มีรายละเอียดเพิ่มเติม
สำหรับพวกคุณอย่างไรก็ตามในสถานการณ์นั้นมีทางเลือกอื่น willMove(toParentViewController:)
การตรวจสอบเมื่อมีการควบคุมดูจะถูกโผล่เป็นไปได้โดยเอาชนะ แนวคิดพื้นฐานคือว่าควบคุมดูจะถูกโผล่เมื่อเป็นparent
nil
ดู"การติดตั้งตัวควบคุมมุมมองคอนเทนเนอร์"สำหรับรายละเอียดเพิ่มเติม
ตั้งแต่ iOS 5 ฉันพบว่าวิธีที่ง่ายที่สุดในการจัดการกับสถานการณ์นี้คือการใช้วิธีการใหม่- (BOOL)isMovingFromParentViewController
:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
// Do your stuff here
}
}
- (BOOL)isMovingFromParentViewController
เหมาะสมเมื่อคุณกำลังผลักและเปิดตัวควบคุมในสแตกการนำทาง
อย่างไรก็ตามหากคุณกำลังนำเสนอตัวควบคุมมุมมองโมดอลคุณควรใช้- (BOOL)isBeingDismissed
แทน:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isBeingDismissed) {
// Do your stuff here
}
}
ดังที่ระบุไว้ในคำถามนี้คุณสามารถรวมคุณสมบัติทั้งสอง:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController || self.isBeingDismissed) {
// Do your stuff here
}
}
โซลูชันอื่น ๆ ขึ้นอยู่กับการมีอยู่ของไฟล์UINavigationBar
. แทนที่จะชอบแนวทางของฉันมากกว่าเพราะมันแยกงานที่จำเป็นในการดำเนินการออกจากการกระทำที่เรียกเหตุการณ์นั่นคือการกดปุ่มย้อนกลับ
self.isMovingFromParentViewController
มีค่า TRUE เมื่อฉันเปิดสแต็กการนำทางโดยใช้โปรแกรมpopToRootViewControllerAnimated
- โดยไม่ต้องแตะปุ่มย้อนกลับ ฉันควรลงคะแนนคำตอบของคุณหรือไม่? (เรื่องบอกว่ากดปุ่ม "ย้อนกลับ" บนแถบนำทาง ")
override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController(){ println("back button pressed") } }
-viewDidDisappear:
เนื่องจากเป็นไปได้ที่คุณจะได้รับ-viewWillDisappear:
โดยไม่ต้อง-viewDidDisappear:
(เช่นเมื่อคุณเริ่มปัดเพื่อปิดรายการตัวควบคุมการนำทางแล้วยกเลิกการปัดนั้น
ในขณะที่viewWillAppear()
และviewDidDisappear()
ถูกเรียกเมื่อแตะปุ่มย้อนกลับปุ่มเหล่านี้จะถูกเรียกในเวลาอื่นด้วย ดูท้ายคำตอบสำหรับข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนั้น
การตรวจจับปุ่มย้อนกลับทำได้ดีกว่าเมื่อ VC ถูกลบออกจากพาเรนต์ (NavigationController) ด้วยความช่วยเหลือของwillMoveToParentViewController(_:)
ORdidMoveToParentViewController()
หากพาเรนต์เป็นศูนย์ตัวควบคุมมุมมองจะถูกดึงออกจากสแต็กการนำทางและปิด หากพาเรนต์ไม่ใช่ศูนย์ระบบจะเพิ่มลงในสแต็กและนำเสนอ
// Objective-C
-(void)willMoveToParentViewController:(UIViewController *)parent {
[super willMoveToParentViewController:parent];
if (!parent){
// The back button was pressed or interactive gesture used
}
}
// Swift
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
if parent == nil {
// The back button was pressed or interactive gesture used
}
}
สลับออกwillMove
สำหรับdidMove
และตรวจสอบ self.parent จะทำผลงานหลังจากควบคุมดูจะถูกไล่ออก
โปรดทราบว่าการตรวจสอบผู้ปกครองไม่อนุญาตให้คุณ "หยุด" การเปลี่ยนแปลงชั่วคราวหากคุณจำเป็นต้องบันทึก async บางประเภท ในการทำเช่นนั้นคุณสามารถใช้สิ่งต่อไปนี้ ข้อเสียเพียงอย่างเดียวคือคุณสูญเสียปุ่มย้อนกลับสไตล์ iOS / เคลื่อนไหวที่สวยงาม นอกจากนี้โปรดระวังด้วยท่าทางการปัดแบบโต้ตอบ ใช้สิ่งต่อไปนี้เพื่อจัดการกรณีนี้
var backButton : UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
// Disable the swipe to make sure you get your chance to save
self.navigationController?.interactivePopGestureRecognizer.enabled = false
// Replace the default back button
self.navigationItem.setHidesBackButton(true, animated: false)
self.backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
self.navigationItem.leftBarButtonItem = backButton
}
// Then handle the button selection
func goBack() {
// Here we just remove the back button, you could also disabled it or better yet show an activityIndicator
self.navigationItem.leftBarButtonItem = nil
someData.saveInBackground { (success, error) -> Void in
if success {
self.navigationController?.popViewControllerAnimated(true)
// Don't forget to re-enable the interactive gesture
self.navigationController?.interactivePopGestureRecognizer.enabled = true
}
else {
self.navigationItem.leftBarButtonItem = self.backButton
// Handle the error
}
}
}
หากคุณไม่ได้รับviewWillAppear
viewDidDisappear
ปัญหามาดูตัวอย่างกัน สมมติว่าคุณมีตัวควบคุมมุมมองสามตัว:
ให้ติดตามการโทรในdetailVC
ขณะที่คุณไปจากlistVC
ไปsettingsVC
และกลับไปlistVC
List> Detail (push detailVC) Detail.viewDidAppear
<- ปรากฏ
รายละเอียด> การตั้งค่า (push settingsVC) Detail.viewDidDisappear
<- หายไป
และเมื่อเราย้อนกลับไป ...
การตั้งค่า> รายละเอียด (pop settingsVC) Detail.viewDidAppear
<- ปรากฏ
รายละเอียด> รายการ (pop detailVC) Detail.viewDidDisappear
<- หายไป
สังเกตว่าviewDidDisappear
มีการเรียกหลายครั้งไม่เพียง แต่เมื่อย้อนกลับเท่านั้น แต่ยังรวมถึงเมื่อไปข้างหน้าด้วย สำหรับการดำเนินการที่รวดเร็วซึ่งอาจเป็นที่ต้องการ แต่สำหรับการดำเนินการที่ซับซ้อนมากขึ้นเช่นการเรียกเครือข่ายเพื่อบันทึกอาจไม่ได้
didMoveToParantViewController:
จะทำงานเมื่อมองไม่เห็นมุมมองอีกต่อไป มีประโยชน์สำหรับ iOS7 ด้วย InteractiveGesutre
_ = self.navigationController?.popViewController(animated: true)
ดังนั้นจึงไม่ได้เรียกแค่การกดปุ่มย้อนกลับ ฉันกำลังมองหาสำหรับการโทรที่ทำงานเฉพาะเมื่อกลับถูกกด
วิธีแรก
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (![parent isEqual:self.parentViewController]) {
NSLog(@"Back pressed");
}
}
วิธีที่สอง
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
// back button was pressed. We know this is true because self is no longer
// in the navigation stack.
}
[super viewWillDisappear:animated];
}
ผู้ที่อ้างว่าไม่ได้ผลถือว่าเข้าใจผิด:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
print("we are being popped")
}
}
ใช้งานได้ดี แล้วอะไรคือสาเหตุที่ทำให้เกิดตำนานที่แพร่หลายอย่างนั้น?
ปัญหาน่าจะเกิดจากการดำเนินการที่ไม่ถูกต้องของที่แตกต่างกันวิธีการคือว่าการดำเนินการลืมที่จะโทรwillMove(toParent:)
super
หากคุณใช้งานwillMove(toParent:)
โดยไม่ต้องโทรsuper
ก็self.isMovingFromParent
จะเป็นfalse
และการใช้งานviewWillDisappear
จะล้มเหลว มันไม่ได้ล้มเหลว คุณทำลายมัน
หมายเหตุ:ปัญหาที่แท้จริงคือตัวควบคุมมุมมองที่สองตรวจพบว่าตัวควบคุมมุมมองแรกถูกงัด โปรดดูการอภิปรายทั่วไปเพิ่มเติมที่นี่: Unified UIViewController "กลายเป็นส่วนหน้า" การตรวจจับ?
แก้ไขความคิดเห็นที่แสดงให้เห็นว่านี้ควรจะเป็นมากกว่าviewDidDisappear
viewWillDisappear
true
สำหรับท่าทางป๊อปแบบโต้ตอบ - จากขอบด้านซ้ายของตัวควบคุมมุมมอง - แม้ว่าการปัดจะไม่ได้เปิดเต็มที่ก็ตาม ดังนั้นแทนที่จะเช็คอินwillDisappear
ให้ทำในdidDisappear
ผลงาน
ฉันเล่น (หรือต่อสู้) กับปัญหานี้มาสองวันแล้ว IMO แนวทางที่ดีที่สุดคือการสร้างคลาสส่วนขยายและโปรโตคอลเช่นนี้:
@protocol UINavigationControllerBackButtonDelegate <NSObject>
/**
* Indicates that the back button was pressed.
* If this message is implemented the pop logic must be manually handled.
*/
- (void)backButtonPressed;
@end
@interface UINavigationController(BackButtonHandler)
@end
@implementation UINavigationController(BackButtonHandler)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
SEL backButtonPressedSel = @selector(backButtonPressed);
if (wasBackButtonClicked && [topViewController respondsToSelector:backButtonPressedSel]) {
[topViewController performSelector:backButtonPressedSel];
return NO;
}
else {
[self popViewControllerAnimated:YES];
return YES;
}
}
@end
วิธีนี้ใช้ได้ผลเพราะUINavigationController
จะได้รับสายเรียกเข้าnavigationBar:shouldPopItem:
ทุกครั้งที่มีการเปิดตัวควบคุมมุมมอง เราตรวจพบว่ามีการกดย้อนกลับหรือไม่ (ปุ่มอื่น ๆ ) สิ่งเดียวที่คุณต้องทำคือใช้โปรโตคอลในตัวควบคุมมุมมองที่กดย้อนกลับ
อย่าลืมเปิดตัวควบคุมมุมมองด้านในด้วยตนเองbackButtonPressedSel
หากทุกอย่างเรียบร้อย
หากคุณมีคลาสย่อยUINavigationViewController
และนำไปใช้navigationBar:shouldPopItem:
แล้วไม่ต้องกังวลสิ่งนี้จะไม่รบกวน
คุณอาจสนใจที่จะปิดการใช้งานท่าทางย้อนกลับ
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
สิ่งนี้ใช้ได้กับฉันใน iOS 9.3.x พร้อม Swift:
override func didMoveToParentViewController(parent: UIViewController?) {
super.didMoveToParentViewController(parent)
if parent == self.navigationController?.parentViewController {
print("Back tapped")
}
}
ไม่เหมือนกับโซลูชันอื่น ๆ ที่นี่ดูเหมือนว่าจะไม่เกิดขึ้นโดยไม่คาดคิด
สำหรับบันทึกฉันคิดว่านี่เป็นสิ่งที่เขากำลังมองหามากกว่า ...
UIBarButtonItem *l_backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(backToRootView:)];
self.navigationItem.leftBarButtonItem = l_backButton;
- (void) backToRootView:(id)sender {
// Perform some custom code
[self.navigationController popToRootViewControllerAnimated:YES];
}
คุณสามารถใช้การโทรกลับของปุ่มย้อนกลับได้ดังนี้:
- (BOOL) navigationShouldPopOnBackButton
{
[self backAction];
return NO;
}
- (void) backAction {
// your code goes here
// show confirmation alert, for example
// ...
}
สำหรับเวอร์ชันที่รวดเร็วคุณสามารถทำสิ่งต่างๆเช่นในขอบเขตทั่วโลก
extension UIViewController {
@objc func navigationShouldPopOnBackButton() -> Bool {
return true
}
}
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}
}
ด้านล่างคุณวางไว้ในตัวควบคุมมุมมองที่คุณต้องการควบคุมการทำงานของปุ่มย้อนกลับ:
override func navigationShouldPopOnBackButton() -> Bool {
self.backAction()//Your action you want to perform.
return true
}
navigationShouldPopOnBackButton
มาจากไหน? ไม่ใช่ส่วนหนึ่งของ API สาธารณะ
ดังที่purrrminator
กล่าวไว้ว่าคำตอบของelitalon
ไม่ถูกต้องอย่างสมบูรณ์เนื่องจากyour stuff
จะถูกดำเนินการแม้ว่าจะเปิดตัวควบคุมโดยใช้โปรแกรม
วิธีแก้ปัญหาที่ฉันพบจนถึงตอนนี้ไม่ค่อยดีนัก แต่ก็ใช้ได้ผลสำหรับฉัน นอกจากสิ่งที่elitalon
กล่าวไปแล้วฉันยังตรวจสอบด้วยว่าฉันกำลังใช้โปรแกรมหรือไม่:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ((self.isMovingFromParentViewController || self.isBeingDismissed)
&& !self.isPoppingProgrammatically) {
// Do your stuff here
}
}
คุณต้องเพิ่มคุณสมบัตินั้นลงในคอนโทรลเลอร์ของคุณและตั้งค่าเป็นใช่ก่อนที่จะเปิดโดยใช้โปรแกรม:
self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];
ขอบคุณสำหรับความช่วยเหลือของคุณ!
วิธีที่ดีที่สุดคือใช้เมธอดผู้แทน UINavigationController
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
เมื่อใช้สิ่งนี้คุณจะสามารถทราบได้ว่าคอนโทรลเลอร์ใดแสดง UINavigationController
if ([viewController isKindOfClass:[HomeController class]]) {
NSLog(@"Show home controller");
}
ฉันได้แก้ปัญหานี้โดยการเพิ่ม UIControl ไปที่ navigationBar ทางด้านซ้าย
UIControl *leftBarItemControl = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 90, 44)];
[leftBarItemControl addTarget:self action:@selector(onLeftItemClick:) forControlEvents:UIControlEventTouchUpInside];
self.leftItemControl = leftBarItemControl;
[self.navigationController.navigationBar addSubview:leftBarItemControl];
[self.navigationController.navigationBar bringSubviewToFront:leftBarItemControl];
และคุณต้องอย่าลืมลบเมื่อมุมมองหายไป:
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.leftItemControl) {
[self.leftItemControl removeFromSuperview];
}
}
นั่นคือทั้งหมด!
คุณควรตรวจสอบUINavigationBarDelegate พิธีสาร ในกรณีนี้คุณอาจต้องการใช้ navigationBar: shouldPopItem: method
ดังที่ Coli88 กล่าวไว้คุณควรตรวจสอบโปรโตคอล UINavigationBarDelegate
โดยทั่วไปแล้วคุณยังสามารถใช้- (void)viewWillDisapear:(BOOL)animated
เพื่อทำงานแบบกำหนดเองเมื่อมุมมองที่เก็บรักษาไว้โดยตัวควบคุมมุมมองที่มองเห็นได้ในปัจจุบันกำลังจะหายไป น่าเสียดายที่สิ่งนี้จะครอบคลุมถึงการผลักดันและกรณีป๊อป
สำหรับ Swift ที่มี UINavigationController:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if self.navigationController?.topViewController != self {
print("back button tapped")
}
}
คำตอบของ7ynk3rนั้นใกล้เคียงกับสิ่งที่ฉันใช้ในตอนท้าย แต่มันต้องมีการปรับแต่ง:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
if (wasBackButtonClicked) {
if ([topViewController respondsToSelector:@selector(navBackButtonPressed)]) {
// if user did press back on the view controller where you handle the navBackButtonPressed
[topViewController performSelector:@selector(navBackButtonPressed)];
return NO;
} else {
// if user did press back but you are not on the view controller that can handle the navBackButtonPressed
[self popViewControllerAnimated:YES];
return YES;
}
} else {
// when you call popViewController programmatically you do not want to pop it twice
return YES;
}
}
self.navigationController.isMovingFromParentViewController ไม่ทำงานอีกต่อไปบน iOS8 และ 9 ฉันใช้:
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.navigationController.topViewController != self)
{
// Is Popping
}
}
ทางออกสุดท้ายที่พบ .. วิธีที่เรากำลังมองหาคือ "willShowViewController" ซึ่งเป็นวิธีการมอบหมายของ UINavigationController
//IMPORT UINavigationControllerDelegate !!
class PushedController: UIViewController, UINavigationControllerDelegate {
override func viewDidLoad() {
//set delegate to current class (self)
navigationController?.delegate = self
}
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
//MyViewController shoud be the name of your parent Class
if var myViewController = viewController as? MyViewController {
//YOUR STUFF
}
}
}
MyViewController
PushedController