Cài đặt hành động cho nút quay lại trong bộ điều khiển điều hướng


180

Tôi đang cố gắng ghi đè hành động mặc định của nút quay lại trong bộ điều khiển điều hướng. Tôi đã cung cấp cho mục tiêu một hành động trên nút tùy chỉnh. Điều kỳ lạ là khi gán nó mặc dù thuộc tính backbutton, nó không chú ý đến chúng và nó chỉ bật chế độ xem hiện tại và quay lại gốc:

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

Ngay khi tôi cài đặt nó leftBarButtonItem, navigationItemnó sẽ gọi hành động của tôi, tuy nhiên sau đó nút này trông giống như một vòng tròn thay vì mũi tên quay lại:

self.navigationItem.leftBarButtonItem = backButton;

Làm cách nào tôi có thể gọi nó để gọi hành động tùy chỉnh của mình trước khi quay lại chế độ xem gốc? Có cách nào để ghi đè hành động quay lại mặc định hoặc có một phương thức luôn được gọi khi rời khỏi chế độ xem ( viewDidUnloadkhông làm như vậy)?


hành động: @selector (nhà)]; cần một: sau hành động chọn: @selector (home :)]; nếu không nó sẽ không hoạt động
PartySoft

7
@PartySoft Điều đó không đúng trừ khi phương thức được khai báo bằng dấu hai chấm. Hoàn toàn hợp lệ khi có các nút chọn bộ chọn mà không lấy bất kỳ tham số nào.
mbm29414


3
Tại sao Apple không cung cấp nút có kiểu dáng giống nút quay lại? Có vẻ khá rõ ràng.
JohnK

Câu trả lời:


363

Hãy thử đặt cái này vào bộ điều khiển xem nơi bạn muốn phát hiện báo chí:

-(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
đây là một cách giải quyết khéo léo, sạch sẽ, tốt đẹp và rất tốt
boliva

6
+1 hack tuyệt vời, nhưng không cung cấp quyền kiểm soát hoạt hình của pop
matm

3
Không hoạt động với tôi nếu tôi gửi tin nhắn cho đại biểu thông qua một nút và đại biểu bật bộ điều khiển - điều này vẫn kích hoạt.
SAHM

21
Một vấn đề khác là bạn không thể phân biệt được nếu người dùng nhấn nút quay lại hoặc nếu bạn gọi một cách lập trình [self.navestionControll popViewControllAnimated: CÓ]
Chase Roberts

10
Chỉ cần một FYI: Swift phiên bản:if (find(self.navigationController!.viewControllers as! [UIViewController],self)==nil)
hEADcRASH

177

Tôi đã triển khai tiện ích mở rộng UIViewControll-BackButtonHandler . Nó không cần phải phân lớp bất cứ thứ gì, chỉ cần đặt nó vào dự án của bạn và ghi đè navigationShouldPopOnBackButtonphương thức trong UIViewControllerlớp:

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

Tải ứng dụng mẫu .


16
Đây là giải pháp sạch nhất tôi từng thấy, tốt hơn và đơn giản hơn so với sử dụng UIButton tùy chỉnh của riêng bạn. Cảm ơn!
ramirogm

4
Đây navigationBar:shouldPopItem:không phải là một phương thức riêng tư vì nó là một phần của UINavigationBarDelegategiao thức.
onegray

1
nhưng UINavestionControll đã triển khai ủy nhiệm (để trả về YES) chưa? hoặc nó sẽ trong tương lai? phân lớp có lẽ là một lựa chọn an toàn hơn
Sam

8
Tôi mới thực hiện điều này (BTW khá thú vị) trong iOS 7.1 và nhận thấy rằng sau khi quay NOlại, nút quay lại vẫn ở trạng thái bị vô hiệu hóa (về mặt trực quan, vì nó vẫn nhận và phản ứng với các sự kiện chạm). Tôi đã làm việc xung quanh nó bằng cách thêm một elsetuyên bố vào shouldPopkiểm tra và đạp xe qua các cuộc phỏng vấn của thanh điều hướng và đặt alphagiá trị trở lại 1 nếu cần trong khối hoạt hình: gist.github.com/idevsoftware/9754057
boliva

2
Đây là một trong những phần mở rộng tốt nhất mà tôi từng thấy. Cảm ơn bạn rất nhiều.
Srikanth

42

Không như Amagrammer nói, điều đó là có thể. Bạn phải phân lớp của bạn navigationController. Tôi đã giải thích mọi thứ ở đây (bao gồm cả mã ví dụ).


Tài liệu của Apple ( developer.apple.com/iphone/l Library / document / UIKit / Khăn ) nói rằng "Lớp này không dành cho phân lớp". Mặc dù tôi không chắc ý của họ là gì - họ có thể có nghĩa là "bạn thường không cần phải làm điều đó" hoặc họ có thể có nghĩa là "chúng tôi sẽ từ chối ứng dụng của bạn nếu bạn gây rối với bộ điều khiển của chúng tôi" ...
Kuba Suder

Đây chắc chắn là cách duy nhất để làm điều đó. Chúc tôi có thể trao cho bạn nhiều điểm hơn Hans!
Adam Eberbach

1
Bạn thực sự có thể ngăn một lượt xem thoát ra bằng phương pháp này không? Điều gì bạn sẽ làm cho phương thức popViewControllAnimated trở lại nếu bạn muốn chế độ xem không thoát?
JosephH

1
Vâng bạn có thể. Chỉ cần không gọi phương thức siêu lớp trong triển khai của bạn, hãy lưu ý! Bạn không nên làm điều đó, người dùng sẽ quay trở lại trong điều hướng. Những gì bạn có thể làm là yêu cầu xác nhận. Theo tài liệu của Apples, popViewControll trả về: "Trình điều khiển khung nhìn được bật từ ngăn xếp." Vì vậy, khi không có gì được bật lên, bạn nên trả về con số không;
HansPinckaers

1
@HansPickaers Tôi nghĩ rằng câu trả lời của bạn về việc ngăn chế độ xem thoát ra có thể không chính xác. Nếu tôi hiển thị thông báo 'xác nhận' từ triển khai lớp con của popViewControllAnimated:, thì NavigationBar vẫn hoạt hình lên một cấp trong cây bất kể tôi trả về cái gì. Điều này có vẻ là do nhấp vào nút quay lại các cuộc gọi nênPopNavlationItem trên thanh điều hướng. Tôi đang trả về nil từ phương thức lớp con của tôi như là giới thiệu.
Deepwinter

15

Phiên bản Swift:

(của https://stackoverflow.com/a/19132881/826435 )

Trong trình điều khiển xem của bạn, bạn chỉ cần tuân thủ một giao thức và thực hiện bất kỳ hành động nào bạn cần:

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

Sau đó, tạo một lớp, nói NavigationController+BackButtonvà chỉ cần sao chép-dán mã dưới đây:

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
    }
}

Có thể tôi đã bỏ lỡ điều gì đó, nhưng nó không hiệu quả với tôi, phương thức biểu diễnSomeActionOnThePressOfABackButton của tiện ích mở rộng không bao giờ được gọi
Turvy

@FlorentBreton có thể là một sự hiểu lầm? shouldPopOnBackButtonPressnên được gọi miễn là không có lỗi. performSomeActionOnThePressOfABackButtonchỉ là một phương thức tạo nên không tồn tại.
kgaidis

Tôi hiểu nó, đó là lý do tại sao tôi tạo một phương thức performSomeActionOnThePressOfABackButtontrong bộ điều khiển của mình để thực hiện một hành động cụ thể khi nhấn nút quay lại, nhưng phương thức này không bao giờ được gọi, hành động đó là quay trở lại bình thường
Turvy

1
Không làm việc cho tôi cũng không. Phương thức ShouldPop không bao giờ được gọi. Bạn đã đặt một đại biểu ở đâu đó?
Tim Autin

@TimAutin Tôi mới thử nghiệm lại lần nữa và dường như có gì đó đã thay đổi. Phần chính để hiểu rằng trong a UINavigationController, navigationBar.delegateđược đặt thành bộ điều khiển điều hướng. Vì vậy, các phương pháp NÊN được gọi. Tuy nhiên, trong Swift, tôi không thể gọi chúng, ngay cả trong một lớp con. Tuy nhiên, tôi đã làm cho chúng được gọi trong Objective-C, vì vậy bây giờ tôi sẽ chỉ sử dụng phiên bản Objective-C. Có thể là một lỗi Swift.
kgaidis

5

Không thể làm trực tiếp. Có một vài lựa chọn thay thế:

  1. Tạo tùy chỉnh của riêng bạn UIBarButtonItem xác thực trên tap và bật nếu thử nghiệm vượt qua
  2. Xác thực nội dung trường biểu mẫu bằng UITextFieldphương thức ủy nhiệm, chẳng hạn như -textFieldShouldReturn:, được gọi sau khi nhấn nút Returnhoặc Donetrên bàn phím

Nhược điểm của tùy chọn đầu tiên là kiểu mũi tên trái của nút quay lại không thể được truy cập từ nút thanh tùy chỉnh. Vì vậy, bạn phải sử dụng một hình ảnh hoặc đi với một nút phong cách thông thường.

Tùy chọn thứ hai rất hay vì bạn lấy lại trường văn bản trong phương thức ủy nhiệm, do đó bạn có thể nhắm mục tiêu logic xác thực của mình đến trường văn bản cụ thể được gửi đến phương thức gọi lại của đại biểu.


5

Đối với một số lý do luồng, giải pháp được đề cập bởi @HansPinckaers không phù hợp với tôi, nhưng tôi đã tìm thấy một cách rất dễ dàng để chạm vào nút quay lại và tôi muốn ghim nó xuống đây trong trường hợp điều này có thể tránh được hàng giờ lừa dối một người nào khác. Thủ thuật này thực sự dễ dàng: chỉ cần thêm một UIButton trong suốt như một khung nhìn phụ vào UINavestionBar của bạn và đặt bộ chọn của bạn cho anh ta như thể đó là nút thực! Đây là một ví dụ sử dụng Monotouch và C #, nhưng bản dịch sang mục tiêu-c không quá khó tìm.

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
    }
}

Sự thật thú vị: để thử nghiệm và để tìm kích thước tốt cho nút giả của tôi, tôi đặt màu nền của nó thành màu xanh ... Và nó hiển thị phía sau nút quay lại! Dù sao, nó vẫn bắt được bất kỳ cảm ứng nào nhắm vào nút gốc.


3

Kỹ thuật này cho phép bạn thay đổi văn bản của nút "quay lại" mà không ảnh hưởng đến tiêu đề của bất kỳ bộ điều khiển xem nào hoặc xem văn bản nút quay lại thay đổi trong khi hoạt hình.

Thêm phần này vào phương thức init trong trình điều khiển khung nhìn cuộc gọi :

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

3

Cách dễ nhất

Bạn có thể sử dụng các phương thức ủy nhiệm của UINavestionControll. Phương thức willShowViewControllerđược gọi khi nhấn nút quay lại của VC của bạn. Làm bất cứ điều gì bạn muốn khi nhấn btn trở lại

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

Đảm bảo rằng trình điều khiển chế độ xem của bạn tự đặt mình là đại biểu của điều hướng điều khiển được kế thừa và tuân thủ giao thức UINavestionControllDelegate
Justin Milo

3

Đây là giải pháp Swift của tôi. Trong lớp con của UIViewControll, ghi đè phương thức navigationShouldPopOnBackButton.

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
    }

}

Ghi đè phương thức điều hướngShouldPopOnBackButton trong UIViewControll không hoạt động - Chương trình thực thi phương thức cha mẹ không phải là phương thức ghi đè. Bất kỳ giải pháp cho điều đó? Có ai có vấn đề tương tự?
Pawel Cala

mọi thứ sẽ quay trở lại rootview nếu trở về đúng sự thật
Pawriwes

@Pawriwes Đây là một giải pháp tôi đã viết có vẻ hiệu quả với tôi: stackoverflow.com/a
432343418/826435

3

Tìm thấy một giải pháp mà vẫn giữ kiểu nút quay lại là tốt. Thêm phương pháp sau vào bộ điều khiển xem của bạn.

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


}

Bây giờ cung cấp một chức năng khi cần thiết trong phương pháp sau:

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

Tất cả những gì nó làm là che nút quay lại bằng một nút trong suốt;)


3

Ghi đè điều hướngBar (_ navigationBar: ShouldPop) : Đây không phải là một ý tưởng hay, ngay cả khi nó hoạt động. Đối với tôi, nó tạo ra các sự cố ngẫu nhiên khi điều hướng trở lại. Tôi khuyên bạn chỉ nên ghi đè nút quay lại bằng cách xóa backButton mặc định khỏi navigationItem và tạo nút quay lại tùy chỉnh như bên dưới:

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

   ...
 
}

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

Dựa trên phản ứng trước với UIAlert trong Swift5 trong một Asynchronous cách


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
    }
}

Trên bộ điều khiển của bạn


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)
            })
            ])
   
    }

}

Bạn có thể cung cấp chi tiết về những gì đang diễn ra với if viewControllers.count <navigationBar.items! .Count {return true} kiểm tra không?
H4Hugo

// Ngăn chặn sự cố đồng bộ hóa xuất hiện quá nhiều mục điều hướng // và không đủ trình điều khiển xem hoặc ngược lại khi khai thác bất thường
brahimm

2

Tôi không tin điều này là có thể, dễ dàng. Cách duy nhất tôi tin để khắc phục điều này là tạo hình ảnh mũi tên nút quay lại của riêng bạn để đặt lên đó. Điều đó làm tôi bực bội lúc đầu nhưng tôi thấy tại sao, vì sự nhất quán, nó lại bị bỏ rơi.

Bạn có thể đến gần (không có mũi tên) bằng cách tạo nút thông thường và ẩn nút quay lại mặc định:

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

2
Vâng, vấn đề là tôi muốn nó trông giống như nút quay lại bình thường, chỉ cần nó gọi hành động tùy chỉnh của tôi trước ...
Vẹt

2

Có một cách dễ dàng hơn bằng cách chỉ subclassing các phương pháp đại biểu của UINavigationBarghi đè lên các ShouldPopItemphương pháp .


Tôi nghĩ bạn có nghĩa là nói lớp con lớp UINavestionControll và thực hiện một phương thức ShouldPopItem. Đó là làm việc tốt cho tôi. Tuy nhiên, phương pháp đó không chỉ đơn giản là trả về CÓ hoặc KHÔNG như bạn mong đợi. Một lời giải thích và giải pháp có sẵn ở đây: stackoverflow.com/a/7453933/462162
arlomedia

2

onegray của giải pháp không được safe.According các tài liệu chính thức của Apple, https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html , chúng ta nên tránh làm điều đó.

"Nếu tên của một phương thức được khai báo trong một thể loại giống như một phương thức trong lớp gốc hoặc một phương thức trong một thể loại khác trên cùng một lớp (hoặc thậm chí là một siêu lớp), thì hành vi không được xác định là việc sử dụng phương thức nào được sử dụng trong thời gian chạy. Điều này ít có khả năng là một vấn đề nếu bạn đang sử dụng các danh mục với các lớp của riêng mình, nhưng có thể gây ra sự cố khi sử dụng các danh mục để thêm phương thức vào các lớp Cacao hoặc Ca cao cảm ứng tiêu chuẩn. "


2

Sử dụng Swift:

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

Kể từ iOS 10 và có lẽ sớm hơn, điều này không còn hoạt động.
Murray Sagal

2

Đây là phiên bản Swift 3 của câu trả lời của @oneway để nắm bắt sự kiện nút quay lại thanh điều hướng trước khi nó được kích hoạt. Vì UINavigationBarDelegatekhông thể được sử dụng cho UIViewController, bạn cần tạo một đại biểu sẽ được kích hoạt khi navigationBar shouldPopđược gọi.

@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
    }
}

Và sau đó, trong trình điều khiển xem của bạn thêm chức năng ủy nhiệm:

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

Tôi đã nhận ra rằng chúng ta thường muốn thêm một bộ điều khiển cảnh báo để người dùng quyết định xem họ có muốn quay lại không. Nếu vậy, bạn luôn có thể return falsetrong navigationShouldPopOnBackButton()chức năng và đóng điều khiển xem của bạn bằng cách làm một cái gì đó như thế này:

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")       
}

Tôi nhận được một lỗi: Value of type 'UIViewController' has no member 'navigationShouldPopOnBackButton' khi tôi cố gắng biên dịch mã của bạn, cho dòng if vc.responds(to: #selector(v...Ngoài ra, self.topViewControllertrả về một tùy chọn và cũng có một cảnh báo cho điều đó.
Sankar

FWIW, tôi đã sửa mã đó bằng cách thực hiện: let vc = self.topViewController as! MyViewControllervà nó dường như hoạt động tốt cho đến nay. Nếu bạn tin rằng đó là một thay đổi đúng, bạn có thể chỉnh sửa mã. Ngoài ra, nếu bạn cảm thấy rằng nó không nên được thực hiện, tôi sẽ vui mừng khi biết lý do tại sao. Cảm ơn mã này. Bạn có lẽ nên viết một bài đăng trên blog về điều này, vì câu trả lời này được chôn vùi theo phiếu bầu.
Sankar

@SankarP Lý do bạn gặp lỗi đó là do bạn MyViewControllercó thể không tuân thủ BackButtonDelegate. Thay vì buộc Unrap, bạn nên làm guard let vc = self.topViewController as? MyViewController else { return true }để tránh sự cố có thể xảy ra.
Lawliet

Cảm ơn. Tôi nghĩ rằng tuyên bố bảo vệ sẽ trở thành: guard let vc = self.topViewController as? MyViewController else { self.popViewController(animated: true) return true }để đảm bảo rằng màn hình đang di chuyển đến đúng trang trong trường hợp nó không thể được sử dụng đúng. Bây giờ tôi hiểu rằng navigationBarhàm được gọi trong tất cả các VC và không chỉ là trình điều khiển khung nhìn nơi mã này tồn tại. Có thể nó sẽ là tốt để cập nhật mã trong câu trả lời của bạn quá? Cảm ơn.
Sankar

2

Phiên bản Swift 4 iOS 11.3:

Điều này được xây dựng dựa trên câu trả lời từ kgaidis từ https://stackoverflow.com/a 432343418/4316579

Tôi không chắc chắn khi tiện ích mở rộng ngừng hoạt động, nhưng tại thời điểm viết bài này (Swift 4), có vẻ như tiện ích mở rộng sẽ không còn được thực thi trừ khi bạn tuyên bố tuân thủ UINavlationBarDelegate như được mô tả bên dưới.

Hy vọng điều này sẽ giúp những người đang tự hỏi tại sao phần mở rộng của họ không còn hoạt động.

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

    }
}

1

Bằng cách sử dụng các biến mục tiêu và hành động mà bạn hiện đang rời khỏi 'nil', bạn sẽ có thể kết nối các hộp thoại lưu của mình để chúng được gọi khi nút được "chọn". Xem ra, điều này có thể được kích hoạt tại những thời điểm kỳ lạ.

Tôi đồng ý chủ yếu với Amagrammer, nhưng tôi không nghĩ việc tạo nút có mũi tên tùy chỉnh sẽ khó đến thế. Tôi sẽ chỉ đổi tên nút quay lại, chụp ảnh màn hình, photoshop kích thước nút cần thiết và đó có phải là hình ảnh trên đầu nút của bạn.


Tôi đồng ý bạn có thể photoshop và tôi nghĩ tôi có thể làm điều này nếu tôi thực sự muốn nó nhưng bây giờ đã quyết định thay đổi giao diện và cảm nhận một chút để làm cho nó hoạt động theo cách tôi muốn.
John Ballinger

Có, ngoại trừ việc các hành động không được kích hoạt khi chúng được gắn vào backBarButtonItem. Tôi không biết đây là lỗi hay tính năng; có thể là ngay cả Apple cũng không biết. Đối với bài tập photoshop, một lần nữa, tôi sẽ cảnh giác rằng Apple sẽ từ chối ứng dụng này vì đã lạm dụng một biểu tượng kinh điển.
Amagrammer

Cảnh báo: câu trả lời này đã được hợp nhất từ ​​một bản sao.
Shog9

1

Bạn có thể thử truy cập vào mục Nút điều hướng bên phải và đặt thuộc tính bộ chọn của nó ... đây là tham chiếu UIBarButtonItem , một điều khác nếu công việc này sẽ làm việc là, hãy đặt mục bên phải của thanh điều hướng thành UIBarButtonItem mà bạn tạo và thiết lập bộ chọn của nó ... hy vọng điều này sẽ giúp


Cảnh báo: câu trả lời này đã được hợp nhất từ ​​một bản sao.
Shog9

1

Đối với một hình thức yêu cầu đầu vào của người dùng như thế này, tôi khuyên bạn nên gọi nó dưới dạng "phương thức" thay vì một phần của ngăn xếp điều hướng của bạn. Bằng cách đó, họ phải chăm sóc doanh nghiệp theo mẫu, sau đó bạn có thể xác thực nó và loại bỏ nó bằng nút tùy chỉnh. Bạn thậm chí có thể thiết kế một thanh điều hướng trông giống như phần còn lại của ứng dụng của bạn nhưng cho phép bạn kiểm soát nhiều hơn.


Cảnh báo: câu trả lời này đã được hợp nhất từ ​​một bản sao.
Shog9

1

Để chặn nút Quay lại, chỉ cần che nó bằng UIControl trong suốt và chặn các cú chạm.

@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

Cảnh báo: câu trả lời này đã được hợp nhất từ ​​một bản sao.
Shog9

1

Ít nhất trong Xcode 5, có một giải pháp đơn giản và khá tốt (không hoàn hảo). Trong IB, kéo Mục Nút Thanh khỏi ngăn Tiện ích và thả nó vào bên trái của Thanh Điều hướng nơi có nút Quay lại. Đặt nhãn thành "Quay lại." Bạn sẽ có một nút chức năng mà bạn có thể liên kết với IBAction của mình và đóng viewContoder của bạn. Tôi đang làm một số công việc và sau đó kích hoạt một segue thư giãn và nó hoạt động hoàn hảo.

Điều không lý tưởng là nút này không nhận được <mũi tên và không chuyển tiếp tiêu đề VC trước đó, nhưng tôi nghĩ rằng điều này có thể được quản lý. Đối với mục đích của tôi, tôi đặt nút Quay lại mới thành nút "Xong" để mục đích rõ ràng.

Bạn cũng kết thúc với hai nút Quay lại trong bộ điều hướng IB, nhưng nó đủ dễ để gắn nhãn cho rõ ràng.

nhập mô tả hình ảnh ở đây


1

Nhanh

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

Cách tiếp cận này hiệu quả với tôi (nhưng nút "Quay lại" sẽ không có dấu "<"):

- (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

Phiên bản Swift của câu trả lời của @ 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
    }
}

Bây giờ trong bất kỳ bộ điều khiển nào, chỉ cần tuân thủ RequestsNavigationPopVerificationvà hành vi này được thông qua theo mặc định.


1

Sử dụng isMovingFromParentViewController

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

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

Bạn có thể giải thích thêm làm thế nào điều này chứng tỏ nút quay lại đã được gõ?
Murray Sagal

Điều này rất đơn giản, nhưng nó chỉ hoạt động nếu bạn chắc chắn quay lại chế độ xem đó từ bất kỳ chế độ xem con nào bạn có thể tải. Nếu đứa trẻ bỏ qua chế độ xem này khi nó quay trở lại cha mẹ, mã của bạn sẽ không được gọi (chế độ xem đã biến mất mà không phải di chuyển khỏi cha mẹ). Nhưng đó là vấn đề tương tự khi chỉ xử lý các sự kiện khi kích hoạt nút Quay lại theo yêu cầu của OP. Vì vậy, đây là một câu trả lời đơn giản cho câu hỏi của mình.
CMont

Đây là siêu đơn giản và thanh lịch. Tôi thích nó. Chỉ có một vấn đề: điều này cũng sẽ kích hoạt nếu người dùng vuốt để quay lại, ngay cả khi họ hủy giữa chừng. Có lẽ một giải pháp tốt hơn sẽ là đưa mã này vào viewDidDisappear. Bằng cách đó, nó sẽ chỉ bắn khi chế độ xem chắc chắn không còn nữa.
Phontaine Judd

1

Tuy nhiên, câu trả lời từ @William là chính xác, nếu người dùng bắt đầu cử chỉ vuốt để quay lại viewWillDisappear phương thức được gọi và thậm chí selfsẽ không nằm trong ngăn xếp điều hướng (nghĩa là self.navigationController.viewControllerssẽ không chứa self), ngay cả khi vuốt chưa hoàn thành và trình điều khiển xem không thực sự xuất hiện. Vì vậy, giải pháp sẽ là:

  1. Vô hiệu hóa cử chỉ vuốt để quay lại viewDidAppearvà chỉ cho phép sử dụng nút quay lại, bằng cách sử dụng:

    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
  2. Hoặc đơn giản là sử dụng viewDidDisappearthay thế, như sau:

    - (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

Giải pháp tôi tìm thấy cho đến nay không phải là rất tốt, nhưng nó hiệu quả với tôi. Nhận câu trả lời này , tôi cũng kiểm tra xem tôi có bật chương trình hay không:

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

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

Bạn phải thêm thuộc tính đó vào bộ điều khiển của mình và đặt nó thành CÓ trước khi bật chương trình:

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

0

Tìm thấy cách mới để làm điều đó:

Mục tiêu-C

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

Nhanh

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.