การตั้งค่าการกระทำสำหรับปุ่มย้อนกลับในตัวควบคุมทิศทาง


180

ฉันพยายามเขียนทับการกระทำเริ่มต้นของปุ่มย้อนกลับในตัวควบคุมทิศทาง ฉันได้กำหนดเป้าหมายให้กับการกระทำบนปุ่มที่กำหนดเอง สิ่งที่แปลกคือเมื่อกำหนดมันแม้ว่าแอตทริบิวต์ backbutton มันไม่ได้ใส่ใจกับพวกเขาและมันก็ปรากฏมุมมองปัจจุบันและกลับไปที่ราก:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] 
                                  initWithTitle: @"Servers" 
                                  style:UIBarButtonItemStylePlain 
                                  target:self 
                                  action:@selector(home)];
self.navigationItem.backBarButtonItem = backButton;

ทันทีที่ฉันตั้งค่าผ่านทางleftBarButtonItemบนnavigationItemมันเรียกการกระทำของฉัน แต่แล้วปุ่มดูเหมือนรอบหนึ่งแทนการกดลูกศรกลับหนึ่ง:

self.navigationItem.leftBarButtonItem = backButton;

ฉันจะให้มันเรียกการกระทำที่กำหนดเองของฉันก่อนที่จะกลับไปที่มุมมองรูตได้อย่างไร มีวิธีการเขียนทับการกระทำกลับเป็นค่าเริ่มต้นหรือมีวิธีที่มักจะเรียกเมื่อออกจากมุมมอง ( viewDidUnloadไม่ทำอย่างนั้น)?


ดำเนินการ: @selector (บ้าน)]; ต้องการ a: หลังจากแอ็คชันของตัวเลือก: @selector (home :)]; ไม่เช่นนั้นจะไม่ทำงาน
PartySoft

7
@PartySoft ไม่เป็นความจริงเว้นแต่ว่ามีการประกาศวิธีการด้วยโคลอน มันถูกต้องสมบูรณ์แบบที่จะมีปุ่มเลือกตัวเลือกการเรียกที่ไม่ใช้พารามิเตอร์ใด ๆ
mbm29414

ซ้ำกันเป็นไปได้ของการหยุด self.navigationItem.leftBarButtonItem จากออกจากมุมมอง
7116

3
ทำไม Apple ถึงไม่มีปุ่มที่มีสไตล์เหมือนปุ่มย้อนกลับ ดูเหมือนจะค่อนข้างชัดเจน
JohnK

คำตอบ:


363

ลองวางสิ่งนี้ลงในตัวควบคุมมุมมองที่คุณต้องการตรวจจับข่าว:

-(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];
}

1
นี่เป็นวิธีการที่ลื่นสะอาดดีและดีมาก ๆ
โบลิวา

6
+1 แฮ็คที่ยอดเยี่ยม แต่ไม่ได้เสนอการควบคุมแอนิเมชั่นของป๊อป
แมท

3
ใช้งานไม่ได้สำหรับฉันถ้าฉันส่งข้อความไปยังผู้รับมอบสิทธิ์ผ่านปุ่มและผู้รับมอบหมายปรากฏตัวควบคุม - นี่ยังคงเป็นไฟ
SAHM

21
ปัญหาอีกประการหนึ่งคือคุณไม่สามารถแยกความแตกต่างได้หากผู้ใช้กดปุ่มย้อนกลับหรือถ้าคุณเรียกโปรแกรมแบบ [self.navigationController popViewControllerAnimated: YES]
Chase Roberts

10
เพียงแค่ FYI: เวอร์ชั่นที่รวดเร็ว:if (find(self.navigationController!.viewControllers as! [UIViewController],self)==nil)
hEADcRASH

177

ฉันใช้ส่วนขยายUIViewController-BackButtonHandler มันไม่จำเป็นต้อง subclass อะไรเพียงแค่ใส่มันลงในโครงการและnavigationShouldPopOnBackButtonวิธีการแทนที่ในUIViewControllerชั้นเรียนของคุณ:

-(BOOL) navigationShouldPopOnBackButton {
    if(needsShowConfirmation) {
        // Show confirmation alert
        // ...
        return NO; // Ignore 'Back' button this time
    }
    return YES; // Process 'Back' button click and Pop view controler
}

แอปดาวน์โหลดตัวอย่าง


16
นี่เป็นคำตอบที่สะอาดที่สุดที่ฉันเคยเห็นดีกว่าและง่ายกว่าการใช้ UIButton ที่คุณกำหนดเอง ขอบคุณ!
ramirogm

4
navigationBar:shouldPopItem:ไม่ได้เป็นวิธีการส่วนตัวเนื่องจากเป็นส่วนหนึ่งของUINavigationBarDelegateโปรโตคอล
onegray

1
แต่ UINavigationController ใช้งานผู้รับมอบสิทธิ์ (เพื่อส่งคืนแล้วYES) หรือไม่? หรือจะเป็นในอนาคต? การแบ่งคลาสย่อยน่าจะเป็นตัวเลือกที่ปลอดภัยกว่า
Sam

8
ฉันเพิ่งใช้งาน (BTW ที่ค่อนข้างดี) ใน iOS 7.1 และสังเกตเห็นว่าหลังจากกลับNOปุ่มย้อนกลับยังคงอยู่ในสถานะปิดใช้งาน (มองเห็นเพราะมันยังคงได้รับและตอบสนองต่อการสัมผัสเหตุการณ์) ฉันแก้ไขมันโดยการเพิ่มelseคำแถลงในการshouldPopตรวจสอบและขี่จักรยานผ่านหน้าย่อยแถบนำทางและตั้งalphaค่ากลับเป็น 1 หากจำเป็นในบล็อกแอนิเมชั่น: gist.github.com/idevsoftware/9754057
boliva

2
นี่เป็นส่วนเสริมที่ดีที่สุดที่ฉันเคยเห็น ขอบคุณมาก.
Srikanth

42

ซึ่งแตกต่างจาก Amagrammer กล่าวว่าเป็นไปได้ navigationControllerคุณต้องซับคลาสของคุณ ฉันอธิบายทุกอย่างที่นี่ (รวมถึงโค้ดตัวอย่าง)


เอกสารประกอบของ Apple ( developer.apple.com/iphone/library/documentation/UIKit/ ...... ) กล่าวว่า "คลาสนี้ไม่ได้มีไว้สำหรับการทำคลาสย่อย" แม้ว่าฉันไม่แน่ใจว่าพวกเขาหมายถึงอะไร - พวกเขาอาจหมายถึง "โดยปกติคุณไม่จำเป็นต้องทำเช่นนั้น" หรือพวกเขาอาจหมายถึง "เราจะปฏิเสธแอปของคุณหากคุณยุ่งกับคอนโทรลเลอร์ของเรา" ...
Kuba Suder

นี่เป็นวิธีเดียวที่จะทำได้อย่างแน่นอน หวังว่าฉันจะให้คะแนนคุณมากกว่านี้กับฮันส์!
Adam Eberbach

1
คุณสามารถป้องกันไม่ให้มุมมองออกโดยใช้วิธีนี้ได้จริงหรือ สิ่งที่คุณจะทำให้วิธีการ popViewControllerAnimated กลับมาถ้าคุณต้องการให้มุมมองไม่ออก?
JosephH

1
ใช่คุณสามารถ อย่าเรียกวิธีซูเปอร์คลาสในการนำไปใช้ของคุณระวัง! คุณไม่ควรทำเช่นนั้นผู้ใช้คาดว่าจะกลับไปใช้การนำทาง สิ่งที่คุณทำได้คือขอคำยืนยัน ตามที่เอกสารประกอบของแอปเปิ้ล popViewController ส่งคืน: "ตัวควบคุมมุมมองที่แตกจากสแต็ก" ดังนั้นเมื่อไม่มีอะไรถูกตอกคุณควรกลับมาว่างเปล่า
HansPinckaers

1
@HansPickaers ฉันคิดว่าคำตอบของคุณเกี่ยวกับการป้องกันมุมมองจากการออกอาจไม่ถูกต้อง หากฉันแสดงข้อความ 'ยืนยัน' จากการใช้งานคลาสย่อยของ popViewControllerAnimated :, NavigationBar จะยังคงเคลื่อนไหวหนึ่งระดับในทรีโดยไม่คำนึงถึงสิ่งที่ฉันกลับมา สิ่งนี้น่าจะเป็นเพราะการคลิกที่ปุ่มย้อนกลับนั้นควรจะเป็น PopNavigationItem บนแถบนำทาง ฉันคืนค่าศูนย์จากวิธี subclasses ของฉันตามที่แนะนำไว้
deepwinter

15

เวอร์ชั่นของ Swift:

(จากhttps://stackoverflow.com/a/19132881/826435 )

ในมุมมองคอนโทรลเลอร์ของคุณคุณเพียงแค่ปฏิบัติตามโปรโตคอลและดำเนินการตามที่คุณต้องการ:

extension MyViewController: NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress() -> Bool {
        performSomeActionOnThePressOfABackButton()
        return false
    }
}

จากนั้นสร้างคลาสพูดNavigationController+BackButtonแล้วคัดลอกโค้ดด้านล่าง:

protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress() -> Bool
}

extension UINavigationController {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        // Prevents from a synchronization issue of popping too many navigation items
        // and not enough view controllers or viceversa from unusual tapping
        if viewControllers.count < navigationBar.items!.count {
            return true
        }

        // Check if we have a view controller that wants to respond to being popped
        var shouldPop = true
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            shouldPop = viewController.shouldPopOnBackButtonPress()
        }

        if (shouldPop) {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            // Prevent the back button from staying in an disabled state
            for view in navigationBar.subviews {
                if view.alpha < 1.0 {
                    UIView.animate(withDuration: 0.25, animations: {
                        view.alpha = 1.0
                    })
                }
            }

        }

        return false
    }
}

บางทีฉันอาจพลาดอะไรบางอย่าง แต่มันใช้งานไม่ได้สำหรับฉันวิธีการทำงาน SomActionOnThePressOfABack ปุ่มของส่วนขยายไม่เคยถูกเรียก
Turvy

@FlorentBreton อาจเข้าใจผิด? shouldPopOnBackButtonPressควรถูกเรียกตราบใดที่ไม่มีข้อบกพร่อง performSomeActionOnThePressOfABackButtonเป็นเพียงวิธีการแต่งหน้าที่ไม่มีอยู่จริง
kgaidis

ฉันเข้าใจว่านั่นเป็นเหตุผลที่ฉันสร้างวิธีperformSomeActionOnThePressOfABackButtonในคอนโทรลเลอร์เพื่อดำเนินการเฉพาะเมื่อกดปุ่มย้อนกลับ แต่วิธีนี้ไม่เคยถูกเรียกการกระทำนั้นกลับมาปกติ
Turvy

1
ไม่ทำงานสำหรับฉันทั้งสอง ไม่ควรเรียกใช้เมธอด shouldPop คุณตั้งผู้แทนไว้ที่อื่นหรือไม่?
ทิม Autin

@TimAutin ฉันเพิ่งทดสอบมันอีกครั้งและดูเหมือนว่ามีบางสิ่งเปลี่ยนไป ส่วนสำคัญที่จะเข้าใจว่าใน a UINavigationController, navigationBar.delegateถูกตั้งค่าเป็นตัวควบคุมการนำทาง ดังนั้นวิธีการที่ควรจะได้รับเรียก อย่างไรก็ตามใน Swift ฉันไม่สามารถเรียกพวกมันได้แม้ในคลาสย่อย อย่างไรก็ตามฉันทำให้พวกมันถูกเรียกใน Objective-C ดังนั้นฉันจะใช้เวอร์ชัน Objective-C ในตอนนี้ อาจเป็นข้อผิดพลาดที่รวดเร็ว
kgaidis

5

ไม่สามารถทำได้โดยตรง มีทางเลือกสองทาง:

  1. สร้างของคุณเอง UIBarButtonItemเพื่อตรวจสอบความถูกต้องเมื่อแตะและปรากฏถ้าการทดสอบผ่าน
  2. ตรวจสอบความถูกต้องของเนื้อหาของฟิลด์โดยใช้UITextFieldวิธีการมอบหมายเช่น-textFieldShouldReturn:ซึ่งถูกเรียกหลังจากกดปุ่มReturnหรือDoneบนแป้นพิมพ์

ข้อเสียของตัวเลือกแรกคือสไตล์ลูกศรชี้ซ้ายของปุ่มย้อนกลับไม่สามารถเข้าถึงได้จากปุ่มแถบที่กำหนดเอง ดังนั้นคุณต้องใช้ภาพหรือไปกับปุ่มสไตล์ปกติ

ตัวเลือกที่สองนั้นดีเพราะคุณได้รับฟิลด์ข้อความกลับมาในวิธีการมอบหมายดังนั้นคุณสามารถกำหนดเป้าหมายตรรกะการตรวจสอบของคุณไปยังฟิลด์ข้อความเฉพาะที่ส่งไปยังวิธีการโทรกลับผู้รับมอบสิทธิ์


5

สำหรับเหตุผลบางอย่างการแก้ปัญหาที่กล่าวถึงโดย @HansPinckaers ไม่เหมาะกับฉัน แต่ฉันพบวิธีที่ง่ายกว่ามากในการสัมผัสปุ่มย้อนกลับและฉันต้องการตรึงสิ่งนี้ไว้ที่นี่ในกรณีที่สามารถหลีกเลี่ยงการหลอกลวงได้หลายชั่วโมง คนอื่น เคล็ดลับนั้นง่ายมากเพียงเพิ่ม UIButton โปร่งใสเป็นมุมมองย่อยไปยัง UINavigationBar ของคุณและตั้งค่าตัวเลือกให้กับเขาราวกับว่ามันเป็นปุ่มจริง! นี่คือตัวอย่างของการใช้ Monotouch และ C # แต่การแปลไปยังวัตถุประสงค์ -c ไม่ควรหายากเกินไป

public class Test : UIViewController {
    public override void ViewDidLoad() {
        UIButton b = new UIButton(new RectangleF(0, 0, 60, 44)); //width must be adapted to label contained in button
        b.BackgroundColor = UIColor.Clear; //making the background invisible
        b.Title = string.Empty; // and no need to write anything
        b.TouchDown += delegate {
            Console.WriteLine("caught!");
            if (true) // check what you want here
                NavigationController.PopViewControllerAnimated(true); // and then we pop if we want
        };
        NavigationController.NavigationBar.AddSubview(button); // insert the button to the nav bar
    }
}

เรื่องจริง: เพื่อวัตถุประสงค์ในการทดสอบและเพื่อหามิติที่ดีสำหรับปุ่มปลอมของฉันฉันจะตั้งค่าสีพื้นหลังเป็นสีน้ำเงิน ... และมันแสดงอยู่ด้านหลังปุ่มย้อนกลับ! อย่างไรก็ตามมันยังคงจับการสัมผัสใด ๆ ที่กำหนดเป้าหมายปุ่มเดิม


3

เทคนิคนี้ช่วยให้คุณสามารถเปลี่ยนข้อความของปุ่ม "ย้อนกลับ" โดยไม่มีผลกับชื่อของตัวควบคุมมุมมองใด ๆ หรือเห็นการเปลี่ยนแปลงข้อความปุ่มย้อนกลับระหว่างการเคลื่อนไหว

เพิ่มสิ่งนี้ไปยังวิธีการเริ่มต้นในตัวควบคุมมุมมองการโทร :

UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];   
temporaryBarButtonItem.title = @"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];

3

วิธีที่ง่ายที่สุด

คุณสามารถใช้วิธีการมอบหมายของ UINavigationController วิธีการที่willShowViewControllerเรียกว่าเมื่อปุ่มย้อนกลับของ VC ของคุณถูกกดทำสิ่งที่คุณต้องการเมื่อกด btn กลับ

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;

ตรวจสอบให้แน่ใจว่าคอนโทรลเลอร์มุมมองของคุณตั้งค่าตัวเองเป็นตัวแทนของ navigationController ที่สืบทอดมาและสอดคล้องกับโปรโตคอล UINavigationControllerDelegate
Justin Milo

3

นี่คือโซลูชัน Swift ของฉัน ในคลาสย่อยของ UIViewController ให้แทนที่วิธีนำทางShouldPopOnBackButton

extension UIViewController {
    func navigationShouldPopOnBackButton() -> Bool {
        return true
    }
}

extension UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {
        if let vc = self.topViewController {
            if vc.navigationShouldPopOnBackButton() {
                self.popViewControllerAnimated(true)
            } else {
                for it in navigationBar.subviews {
                    let view = it as! UIView
                    if view.alpha < 1.0 {
                        [UIView .animateWithDuration(0.25, animations: { () -> Void in
                            view.alpha = 1.0
                        })]
                    }
                }
                return false
            }
        }
        return true
    }

}

การแทนที่เมธอด navigationShouldPopOnBackButton ใน UIViewController ไม่ทำงาน - โปรแกรมเรียกใช้เมธอด parent ไม่ใช่ overriden มีทางออกอะไรบ้าง? ใครบ้างมีปัญหาเดียวกัน
Pawel Cala

มันจะไปตลอดทางกลับสู่รูทวิวหากผลตอบแทนจริง
Pawriwes

@Pawriwes ต่อไปนี้เป็นวิธีแก้ปัญหาที่ฉันเขียนขึ้นซึ่งดูเหมือนว่าจะเหมาะกับฉัน: stackoverflow.com/a/34343418/826435
kgaidis

3

พบวิธีแก้ปัญหาซึ่งยังคงรูปแบบปุ่มย้อนกลับเช่นกัน เพิ่มวิธีการต่อไปนี้ไปยังตัวควบคุมมุมมองของคุณ

-(void) overrideBack{

    UIButton *transparentButton = [[UIButton alloc] init];
    [transparentButton setFrame:CGRectMake(0,0, 50, 40)];
    [transparentButton setBackgroundColor:[UIColor clearColor]];
    [transparentButton addTarget:self action:@selector(backAction:) forControlEvents:UIControlEventTouchUpInside];
    [self.navigationController.navigationBar addSubview:transparentButton];


}

ตอนนี้มีฟังก์ชันการทำงานตามต้องการในวิธีการต่อไปนี้:

-(void)backAction:(UIBarButtonItem *)sender {
    //Your functionality
}

สิ่งที่มันทำคือการปกปิดปุ่มย้อนกลับด้วยปุ่มโปร่งใส;)


3

การแทนที่ navigationBar (_ navigationBar: shouldPop) : นี่ไม่ใช่ความคิดที่ดีแม้ว่าจะใช้งานได้ สำหรับฉันมันสร้างความผิดพลาดแบบสุ่มในการนำทางด้านหลัง ฉันแนะนำให้คุณแทนที่ปุ่มย้อนกลับโดยลบ backButton เริ่มต้นจาก navigationItem และสร้างปุ่มย้อนกลับที่กำหนดเองเช่นด้านล่าง:

override func viewDidLoad(){
   super.viewDidLoad()
   
   navigationItem.leftBarButton = .init(title: "Go Back", ... , action: #selector(myCutsomBackAction) 

   ...
 
}

========================================

สร้างการตอบสนองก่อนหน้านี้ด้วยUIAlertในSwift5ในAsynchronousวิธี


protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ())
}

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
      
        if viewControllers.count < navigationBar.items!.count {
            return true
        }
        
        // Check if we have a view controller that wants to respond to being popped
        
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            
            viewController.shouldPopOnBackButtonPress { shouldPop in
                if (shouldPop) {
                    /// on confirm => pop
                    DispatchQueue.main.async {
                        self.popViewController(animated: true)
                    }
                } else {
                    /// on cancel => do nothing
                }
            }
            /// return false => so navigator will cancel the popBack
            /// until user confirm or cancel
            return false
        }else{
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        }
        return true
    }
}

บนตัวควบคุมของคุณ


extension MyController: NavigationControllerBackButtonDelegate {
    
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ()) {
    
        let msg = "message"
        
        /// show UIAlert
        alertAttention(msg: msg, actions: [
            
            .init(title: "Continuer", style: .destructive, handler: { _ in
                completion(true)
            }),
            .init(title: "Annuler", style: .cancel, handler: { _ in
                completion(false)
            })
            ])
   
    }

}

คุณสามารถให้รายละเอียดเกี่ยวกับสิ่งที่เกิดขึ้นกับ if viewControllers.count <navigationBar.items! .count {return true} โปรดได้ไหม
H4Hugo

// ป้องกันไม่ให้ปัญหาการซิงโครไนซ์ของ popping รายการการนำทางมากเกินไป // และดูตัวควบคุมหรือ
วิกิพีเดีย

2

ฉันไม่เชื่อว่ามันเป็นไปได้อย่างง่ายดาย วิธีเดียวที่ฉันเชื่อว่าจะได้รับรอบนี้คือการทำให้ภาพลูกศรปุ่มย้อนกลับของคุณเองเพื่อวางที่นั่น มันน่าผิดหวังสำหรับฉันในตอนแรก แต่ฉันเห็นว่าทำไมเพื่อความมั่นคงมันก็ถูกทิ้งไว้

คุณสามารถเข้าใกล้ (โดยไม่มีลูกศร) โดยสร้างปุ่มปกติและซ่อนปุ่มย้อนกลับเริ่มต้น:

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;

2
ใช่ปัญหาคือผมต้องการให้มีลักษณะเหมือนปุ่มย้อนกลับตามปกติเพียงแค่ต้องการที่จะเรียกการกระทำของฉันเองแรก ...
นกแก้ว

2

มีวิธีที่ง่ายกว่าคือโดยเพียงแค่ subclassing วิธีผู้รับมอบสิทธิ์ของUINavigationBarและแทนที่วิธีShouldPopItem


ฉันคิดว่าคุณหมายถึงพูด subclass คลาส UINavigationController และใช้เมธอด shouldPopItem นั่นทำงานได้ดีสำหรับฉัน อย่างไรก็ตามวิธีการนั้นไม่ควรเพียงแค่คืนค่าใช่หรือไม่ใช่ตามที่คุณคาดหวัง คำอธิบายและวิธีแก้ไขมีอยู่ที่นี่: stackoverflow.com/a/7453933/462162
arlomedia

2

โซลูชันของ onegray นั้นไม่ปลอดภัยตามเอกสารอย่างเป็นทางการของ Apple https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.htmlเราควรหลีกเลี่ยงการทำเช่นนั้น

"ถ้าชื่อของวิธีการที่ประกาศในหมวดหมู่นั้นเหมือนกันกับวิธีการในชั้นเรียนดั้งเดิมหรือวิธีการในหมวดหมู่อื่นในชั้นเรียนเดียวกัน (หรือแม้กระทั่งซูเปอร์คลาส) พฤติกรรมจะไม่ได้กำหนดว่าจะใช้วิธีการใดบ้าง ที่ runtime สิ่งนี้มีโอกาสน้อยกว่าที่จะเป็นปัญหาหากคุณใช้หมวดหมู่กับคลาสของคุณเอง แต่อาจทำให้เกิดปัญหาเมื่อใช้หมวดหมู่เพื่อเพิ่มวิธีในคลาส Cocoa หรือ Cocoa Touch แบบมาตรฐาน "


2

ใช้ Swift:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}

ตั้งแต่ iOS 10 และก่อนหน้านี้อาจใช้งานไม่ได้อีกต่อไป
Murray Sagal

2

นี่คือคำตอบของ@onewayรุ่น Swift 3 สำหรับการจับปุ่มย้อนกลับของแถบนำทางเหตุการณ์ก่อนที่จะถูกไล่ออก เนื่องจากUINavigationBarDelegateไม่สามารถใช้งานได้UIViewControllerคุณจะต้องสร้างผู้รับมอบสิทธิ์ที่จะถูกเรียกใช้เมื่อnavigationBar shouldPopมีการโทร

@objc public protocol BackButtonDelegate {
      @objc optional func navigationShouldPopOnBackButton() -> Bool 
}

extension UINavigationController: UINavigationBarDelegate  {

    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

        if viewControllers.count < (navigationBar.items?.count)! {                
            return true
        }

        var shouldPop = true
        let vc = self.topViewController

        if vc.responds(to: #selector(vc.navigationShouldPopOnBackButton)) {
            shouldPop = vc.navigationShouldPopOnBackButton()
        }

        if shouldPop {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            for subView in navigationBar.subviews {
                if(0 < subView.alpha && subView.alpha < 1) {
                    UIView.animate(withDuration: 0.25, animations: {
                        subView.alpha = 1
                    })
                }
            }
        }

        return false
    }
}

จากนั้นในตัวควบคุมมุมมองของคุณเพิ่มฟังก์ชันผู้แทน:

class BaseVC: UIViewController, BackButtonDelegate {
    func navigationShouldPopOnBackButton() -> Bool {
        if ... {
            return true
        } else {
            return false
        }        
    }
}

ฉันรู้ว่าเรามักจะต้องการเพิ่มตัวควบคุมการแจ้งเตือนสำหรับผู้ใช้ในการตัดสินใจว่าพวกเขาต้องการที่จะกลับไป ถ้าเป็นเช่นนั้นคุณจะสามารถreturn falseในnavigationShouldPopOnBackButton()การทำงานและปิดควบคุมมุมมองของคุณด้วยการทำอะไรเช่นนี้

func navigationShouldPopOnBackButton() -> Bool {
     let alert = UIAlertController(title: "Warning",
                                          message: "Do you want to quit?",
                                          preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { UIAlertAction in self.yes()}))
            alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { UIAlertAction in self.no()}))
            present(alert, animated: true, completion: nil)
      return false
}

func yes() {
     print("yes")
     DispatchQueue.main.async {
            _ = self.navigationController?.popViewController(animated: true)
        }
}

func no() {
    print("no")       
}

ฉันได้รับข้อผิดพลาด: Value of type 'UIViewController' has no member 'navigationShouldPopOnBackButton' เมื่อฉันพยายามคอมไพล์โค้ดของคุณสำหรับบรรทัดif vc.responds(to: #selector(v...นั้นself.topViewControllerผลตอบแทนจะเป็นตัวเลือกและมีคำเตือนสำหรับสิ่งนั้นด้วย
Sankar

FWIW ฉันได้แก้ไขรหัสนั้นโดยการทำ: let vc = self.topViewController as! MyViewControllerและดูเหมือนว่าจะทำงานได้ดีจนถึงตอนนี้ หากคุณเชื่อว่าเป็นการเปลี่ยนแปลงที่ถูกต้องคุณสามารถแก้ไขรหัสได้ นอกจากนี้หากคุณรู้สึกว่าไม่ควรทำฉันยินดีที่จะรู้ว่าทำไม ขอบคุณสำหรับรหัสนี้ คุณควรเขียนโพสต์บล็อกเกี่ยวกับเรื่องนี้เนื่องจากคำตอบนี้ฝังอยู่ตามคะแนนโหวต
Sankar

@SankarP เหตุผลที่คุณได้รับข้อผิดพลาดที่คุณอาจจะไม่สอดคล้องกับMyViewController BackButtonDelegateแทนที่จะบังคับให้แกะห่อคุณควรทำguard let vc = self.topViewController as? MyViewController else { return true }เพื่อหลีกเลี่ยงความผิดพลาดที่อาจเกิดขึ้น
Lawliet

ขอบคุณ ฉันคิดว่าคำแถลงยามควรจะกลายเป็น: guard let vc = self.topViewController as? MyViewController else { self.popViewController(animated: true) return true }เพื่อให้แน่ใจว่าหน้าจอกำลังเคลื่อนไปยังหน้าขวาในกรณีที่ไม่สามารถส่งได้อย่างถูกต้อง ตอนนี้ฉันเข้าใจแล้วว่าnavigationBarฟังก์ชั่นนั้นถูกเรียกใช้ใน VCs ทั้งหมดไม่ใช่แค่ตัวควบคุม view ที่มีรหัสนี้อยู่ มันอาจจะเป็นการดีที่จะปรับปรุงรหัสในคำตอบของคุณด้วย? ขอบคุณ
Sankar

2

เวอร์ชั่น Swift 4 iOS 11.3:

สิ่งนี้สร้างขึ้นจากคำตอบของ kgaidis จากhttps://stackoverflow.com/a/34343418/4316579

ฉันไม่แน่ใจว่าเมื่อใดที่ส่วนขยายหยุดทำงาน แต่ในขณะที่เขียน (Swift 4) นี้ปรากฏว่าส่วนขยายนั้นจะไม่ถูกดำเนินการอีกต่อไปเว้นแต่คุณจะประกาศความสอดคล้อง UINavigationBarDelegate ดังที่อธิบายไว้ด้านล่าง

หวังว่านี่จะช่วยให้ผู้คนที่สงสัยว่าทำไมส่วนขยายของพวกเขาไม่ทำงานอีกต่อไป

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

    }
}

1

โดยการใช้ตัวแปรเป้าหมายและการกระทำที่คุณกำลังออกจาก 'ไม่มี' คุณควรจะสามารถวางสายไดอะล็อกการบันทึกของคุณเพื่อที่พวกเขาจะถูกเรียกเมื่อปุ่ม "เลือก" ระวังสิ่งนี้อาจถูกกระตุ้นในช่วงเวลาที่แปลก

ฉันเห็นด้วยกับ Amagrammer เป็นส่วนใหญ่ แต่ฉันไม่คิดว่ามันจะเป็นการยากที่จะสร้างปุ่มด้วยลูกศรที่กำหนดเอง ฉันจะเปลี่ยนชื่อปุ่มย้อนกลับถ่ายภาพหน้าจอปรับขนาดของปุ่ม Photoshop และให้เป็นรูปภาพที่ด้านบนของปุ่มของคุณ


ฉันเห็นด้วยที่คุณสามารถทำ Photoshop และฉันคิดว่าฉันอาจทำเช่นนี้ถ้าฉันต้องการมันจริงๆ แต่ตอนนี้ได้ตัดสินใจที่จะเปลี่ยนรูปลักษณ์และความรู้สึกเล็ก ๆ น้อย ๆ เพื่อให้ได้งานนี้ในแบบที่ฉันต้องการ
John Ballinger

ใช่ยกเว้นว่าการกระทำจะไม่ถูกกระตุ้นเมื่อแนบกับ backBarButtonItem ฉันไม่รู้ว่านี่เป็นข้อผิดพลาดหรือคุณสมบัติ; เป็นไปได้ว่าแม้ Apple จะไม่รู้ สำหรับการออกกำลังกายการถ่ายภาพฉันต้องระวังอีกครั้งว่า Apple จะปฏิเสธแอปพลิเคชั่นที่ใช้สัญลักษณ์แบบบัญญัติ
Amagrammer

Heads-up: คำตอบนี้ถูกรวมเข้าด้วยกันจากคำซ้ำซ้อน
Shog9

1

คุณสามารถลองเข้าถึงรายการ NavigationBars ปุ่มขวาและตั้งค่าคุณสมบัติตัวเลือก ... นี่คือการอ้างอิงUIBarButtonItem การอ้างอิงสิ่งอื่นถ้าสิ่งนี้ไม่ทำงานที่จะ def งานคือการตั้งค่ารายการปุ่มขวาของแถบ nav เป็น UIBarButtonItem ที่คุณกำหนดเอง สร้างและตั้งค่าตัวเลือก ... หวังว่านี่จะช่วยได้


Heads-up: คำตอบนี้ถูกรวมเข้าด้วยกันจากคำซ้ำซ้อน
Shog9

1

สำหรับรูปแบบที่ต้องการให้ผู้ใช้ป้อนเช่นนี้ฉันขอแนะนำให้เรียกใช้เป็น "modal" แทนที่จะเป็นส่วนหนึ่งของกองการนำทางของคุณ ด้วยวิธีนี้พวกเขาจะต้องดูแลธุรกิจในแบบฟอร์มจากนั้นคุณสามารถตรวจสอบได้และยกเลิกโดยใช้ปุ่มที่กำหนดเอง คุณสามารถออกแบบแถบนำทางที่มีลักษณะเหมือนกับแอพที่เหลือของคุณ แต่ให้การควบคุมที่มากขึ้น


Heads-up: คำตอบนี้ถูกรวมเข้าด้วยกันจากคำซ้ำซ้อน
Shog9

1

ในการดักปุ่ม Back ให้ครอบคลุมด้วย UIControl ที่โปร่งใสและตัดการสัมผัส

@interface MyViewController : UIViewController
{
    UIControl   *backCover;
    BOOL        inhibitBackButtonBOOL;
}
@end

@implementation MyViewController
-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // Cover the back button (cannot do this in viewWillAppear -- too soon)
    if ( backCover == nil ) {
        backCover = [[UIControl alloc] initWithFrame:CGRectMake( 0, 0, 80, 44)];
#if TARGET_IPHONE_SIMULATOR
        // show the cover for testing
        backCover.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.15];
#endif
        [backCover addTarget:self action:@selector(backCoverAction) forControlEvents:UIControlEventTouchDown];
        UINavigationBar *navBar = self.navigationController.navigationBar;
        [navBar addSubview:backCover];
    }
}

-(void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    [backCover removeFromSuperview];
    backCover = nil;
}

- (void)backCoverAction
{
    if ( inhibitBackButtonBOOL ) {
        NSLog(@"Back button aborted");
        // notify the user why...
    } else {
        [self.navigationController popViewControllerAnimated:YES]; // "Back"
    }
}
@end

Heads-up: คำตอบนี้ถูกรวมเข้าด้วยกันจากคำซ้ำซ้อน
Shog9

1

อย่างน้อยใน Xcode 5 มีวิธีการแก้ปัญหาที่ง่ายและไม่ดี (ไม่สมบูรณ์) ใน IB ให้ลากรายการปุ่มบาร์ออกจากบานหน้าต่างยูทิลิตี้แล้ววางลงที่ด้านซ้ายของแถบการนำทางที่ปุ่มย้อนกลับจะเป็น ตั้งฉลากเป็น "ย้อนกลับ" คุณจะมีปุ่มใช้งานที่คุณสามารถผูกกับ IBAction ของคุณและปิด viewController ของคุณ ฉันกำลังทำงานบางอย่างและจากนั้นก็เรียกใช้งานการผ่อนคลายและทำงานได้อย่างสมบูรณ์แบบ

สิ่งที่ไม่เหมาะคือปุ่มนี้ไม่ได้รับ <ลูกศรและไม่นำชื่อ VCs ก่อนหน้ามาใช้ แต่ฉันคิดว่านี่สามารถจัดการได้ สำหรับวัตถุประสงค์ของฉันฉันตั้งค่าปุ่มย้อนกลับใหม่ให้เป็นปุ่ม "เสร็จสิ้น" เพื่อวัตถุประสงค์ที่ชัดเจน

คุณยังท้ายด้วยปุ่ม Back สองปุ่มในเนวิเกเตอร์ IB แต่มันง่ายพอที่จะติดป้ายเพื่อความชัดเจน

ป้อนคำอธิบายรูปภาพที่นี่


1

รวดเร็ว

override func viewWillDisappear(animated: Bool) {
    let viewControllers = self.navigationController?.viewControllers!
    if indexOfArray(viewControllers!, searchObject: self) == nil {
        // do something
    }
    super.viewWillDisappear(animated)
}

func indexOfArray(array:[AnyObject], searchObject: AnyObject)-> Int? {
    for (index, value) in enumerate(array) {
        if value as UIViewController == searchObject as UIViewController {
            return index
        }
    }
    return nil
}

1

วิธีนี้ใช้ได้ผลสำหรับฉัน (แต่ปุ่ม "ย้อนกลับ" จะไม่มีเครื่องหมาย "<"):

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIBarButtonItem* backNavButton = [[UIBarButtonItem alloc] initWithTitle:@"Back"
                                                                      style:UIBarButtonItemStyleBordered
                                                                     target:self
                                                                     action:@selector(backButtonClicked)];
    self.navigationItem.leftBarButtonItem = backNavButton;
}

-(void)backButtonClicked
{
    // Do something...
    AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    [delegate.navController popViewControllerAnimated:YES];
}

1

คำตอบของ @ onegray เวอร์ชันที่รวดเร็ว

protocol RequestsNavigationPopVerification {
    var confirmationTitle: String { get }
    var confirmationMessage: String { get }
}

extension RequestsNavigationPopVerification where Self: UIViewController {
    var confirmationTitle: String {
        return "Go back?"
    }

    var confirmationMessage: String {
        return "Are you sure?"
    }
}

final class NavigationController: UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {

        guard let requestsPopConfirm = topViewController as? RequestsNavigationPopVerification else {
            popViewControllerAnimated(true)
            return true
        }

        let alertController = UIAlertController(title: requestsPopConfirm.confirmationTitle, message: requestsPopConfirm.confirmationMessage, preferredStyle: .Alert)

        alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                let dimmed = navigationBar.subviews.flatMap { $0.alpha < 1 ? $0 : nil }
                UIView.animateWithDuration(0.25) {
                    dimmed.forEach { $0.alpha = 1 }
                }
            })
            return
        })

        alertController.addAction(UIAlertAction(title: "Go back", style: .Default) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                self.popViewControllerAnimated(true)
            })
        })

        presentViewController(alertController, animated: true, completion: nil)

        return false
    }
}

ขณะนี้อยู่ในคอนโทรลเลอร์ใด ๆ เพียงทำตามRequestsNavigationPopVerificationและพฤติกรรมนี้จะถูกนำมาใช้โดยค่าเริ่มต้น


1

ใช้ isMovingFromParentViewController

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)

    if self.isMovingFromParentViewController {
        // current viewController is removed from parent
        // do some work
    }
}

คุณช่วยอธิบายเพิ่มเติมได้ไหมว่าวิธีนี้พิสูจน์ให้เห็นว่าปุ่มย้อนกลับถูกแตะหรือไม่
Murray Sagal

สิ่งนี้ง่าย แต่จะใช้งานได้ก็ต่อเมื่อคุณแน่ใจว่าจะกลับมาที่มุมมองนั้นจากมุมมองลูกที่คุณอาจโหลด หากเด็กข้ามมุมมองนี้เมื่อกลับไปที่ผู้ปกครองจะไม่มีการเรียกรหัสของคุณ (มุมมองนั้นหายไปแล้วโดยไม่ย้ายจากผู้ปกครอง) แต่นั่นเป็นปัญหาเดียวกันกับการจัดการเหตุการณ์ที่เกิดขึ้นเฉพาะกับการทริกเกอร์ของปุ่มย้อนกลับตามที่ถามโดย OP นี่คือคำตอบง่ายๆสำหรับคำถามของเขา
CMont

นี่คือสุดยอดและเรียบง่าย ฉันรักมัน. เพียงปัญหาเดียว: สิ่งนี้จะเริ่มทำงานหากผู้ใช้เลื่อนหน้าจอเพื่อย้อนกลับแม้ว่าจะยกเลิกกลางคันก็ตาม viewDidDisappearบางทีอาจจะเป็นทางออกที่ดีกว่าจะใส่รหัสนี้ใน ด้วยวิธีนี้มันจะยิงได้ก็ต่อเมื่อมุมมองนั้นหายไปอย่างแน่นอน
Phontaine Judd

1

คำตอบจาก @William ถูกต้องอย่างไรก็ตามหากผู้ใช้เริ่มต้นการเลื่อนนิ้วเพื่อเลื่อนกลับviewWillDisappearวิธีการที่เรียกว่าและแม้selfจะไม่อยู่ในการนำทางสแต็ก (กล่าวคือself.navigationController.viewControllersจะไม่มีself) แม้ว่าการปัด ยังไม่เสร็จสมบูรณ์และตัวควบคุมมุมมองไม่ได้เกิดขึ้นจริง ดังนั้นวิธีแก้ปัญหาคือ:

  1. ปิดการใช้งานการเลื่อนนิ้วเพื่อเลื่อนกลับviewDidAppearและอนุญาตเฉพาะการใช้ปุ่มย้อนกลับโดยใช้:

    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
  2. หรือเพียงใช้viewDidDisappearแทนดังนี้

    - (void)viewDidDisappear:(BOOL)animated
    {
        [super viewDidDisappear:animated];
        if (![self.navigationController.viewControllers containsObject:self])
        {
            // back button was pressed or the the swipe-to-go-back gesture was
            // completed. We know this is true because self is no longer
            // in the navigation stack.
        }
    }

0

วิธีแก้ปัญหาที่ฉันพบมาไม่ดีมาก แต่ใช้งานได้สำหรับฉัน ใช้คำตอบนี้ฉันยังตรวจสอบว่าฉัน popping โดยทางโปรแกรมหรือไม่:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if ((self.isMovingFromParentViewController || self.isBeingDismissed)
      && !self.isPoppingProgrammatically) {
    // Do your stuff here
  }
}

คุณต้องเพิ่มคุณสมบัตินั้นลงในคอนโทรลเลอร์ของคุณและตั้งเป็น YES ก่อนที่จะ popping โดยทางโปรแกรม:

self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];

0

พบวิธีใหม่ในการทำ:

Objective-C

- (void)didMoveToParentViewController:(UIViewController *)parent{
    if (parent == NULL) {
        NSLog(@"Back Pressed");
    }
}

รวดเร็ว

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.