Navigation Controllerの戻るボタンのデフォルトアクションを上書きしようとしています。ターゲットにカスタムボタンのアクションを提供しました。奇妙なことは、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
はそれを行いません)?
プレスを検出したいView Controllerにこれを入れてみてください:
-(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];
}
IViewController-BackButtonHandler 拡張機能を実装しました。サブクラス化する必要はありません。プロジェクトに追加して、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
}
アマグラマーが言ったのとは異なり、それは可能です。 navigationController
をサブクラス化する必要があります。すべてを説明しました ここ (コード例を含む)。
( https://stackoverflow.com/a/19132881/826435 )
View Controllerでは、プロトコルに準拠し、必要なアクションを実行するだけです。
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
}
}
いくつかのスレッド化の理由から、@ HansPinckaersが言及した解決策は私には適切ではありませんでしたが、戻るボタンをタッチする非常に簡単な方法を見つけました。他の誰か。トリックは非常に簡単です。UINavigationBarのサブビューとして透明なUIButtonを追加し、実際のボタンであるかのようにセレクターを彼に設定します。以下にMonotouchとC#を使用した例を示しますが、objective-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
}
}
おもしろい事実:テスト目的と、偽のボタンに適した寸法を見つけるために、背景色を青に設定します...そして、behindbackボタン!とにかく、元のボタンをターゲットとするタッチをキャッチします。
直接行うことはできません。いくつかの選択肢があります。
UIBarButtonItem
を作成します-textFieldShouldReturn:
などのUITextField
デリゲートメソッドを使用して、フォームフィールドの内容を検証します。これは、Return
またはDone
ボタンがキーボードで押された後に呼び出されます最初のオプションの欠点は、戻るボタンの左向き矢印スタイルにカスタムバーボタンからアクセスできないことです。そのため、画像を使用するか、通常のスタイルボタンを使用する必要があります。
2番目のオプションは、デリゲートメソッドでテキストフィールドを取得するため、ニースです。そのため、デリゲートコールバックメソッドに送信される特定のテキストフィールドを検証ロジックの対象にすることができます。
この手法により、View Controllerのタイトルに影響を与えたり、アニメーション中に戻るボタンのテキストが変化したりすることなく、「戻る」ボタンのテキストを変更できます。
これをcalling View Controllerのinitメソッドに追加します。
UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];
temporaryBarButtonItem.title = @"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];
戻るボタンのスタイルも保持するソリューションを見つけました。 View Controllerに次のメソッドを追加します。
-(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
}
それは、透明なボタンで戻るボタンを覆うだけです;)
最も簡単な方法
UINavigationControllerのデリゲートメソッドを使用できます。メソッドwillShowViewController
は、VCの戻るボタンが押されたときに呼び出されます。
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
UINavigationBar
のデリゲートメソッドと、オーバーライドShouldPopItem
-メソッドをサブクラス化するだけの簡単な方法があります。
これは、ナビゲーションバーの戻るボタンイベントが発生する前にキャッチするための@ 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
}
}
次に、View Controllerでデリゲート関数を追加します。
class BaseVC: UIViewController, BackButtonDelegate {
func navigationShouldPopOnBackButton() -> Bool {
if ... {
return true
} else {
return false
}
}
}
ユーザーが戻って行きたいかどうかを判断するために、アラートコントローラーを追加したいことがよくあることに気付きました。その場合、navigationShouldPopOnBackButton()
関数でいつでもreturn false
を実行し、次のような操作を行ってView Controllerを閉じることができます。
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")
}
これが簡単に可能だとは思わない。これを回避する唯一の方法は、自分で戻るボタンの矢印画像を作成してそこに配置することです。最初はイライラしましたが、一貫性のために除外された理由がわかります。
通常のボタンを作成し、デフォルトの戻るボタンを非表示にすることで、(矢印なしで)閉じることができます。
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;
Swiftの使用:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if self.navigationController?.topViewController != self {
print("back button tapped")
}
}
onegrayのソリューションは安全ではありません。Appleの公式ドキュメントによると、 https://developer.Apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html 、weそれを避けるべきです。
「カテゴリで宣言されたメソッドの名前が元のクラスのメソッド、または同じクラス(またはスーパークラス)の別のカテゴリのメソッドと同じ場合、動作はどのメソッド実装が使用されるかについて未定義です独自のクラスでカテゴリを使用している場合、これは問題になる可能性は低くなりますが、カテゴリを使用して標準のCocoaまたはCocoa Touchクラスにメソッドを追加すると問題が発生する可能性があります。
これが私のSwiftソリューションです。 UIViewControllerのサブクラスで、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
}
}
@Williamからの答えは正しいですが、ユーザーがスワイプからゴーバックのジェスチャを開始した場合、viewWillDisappear
メソッドが呼び出され、self
でさえナビゲーションスタックに含まれません(つまり、self.navigationController.viewControllers
にはself
が含まれません)。スワイプが完了せず、View Controllerが実際にポップされない場合でも。したがって、解決策は次のとおりです。
以下を使用して、viewDidAppear
のスワイプゴーバックジェスチャーを無効にし、戻るボタンの使用のみを許可します。
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
{
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
または、次のように、代わりに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.
}
}
このアプローチは私にとってはうまくいきました(ただし、「戻る」ボタンには「<」記号はありません)。
- (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];
}
isMovingFromParentViewController
を使用
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(true)
if self.isMovingFromParentViewController {
// current viewController is removed from parent
// do some work
}
}
このようなユーザー入力を必要とするフォームの場合、ナビゲーションスタックの一部ではなく「モーダル」として呼び出すことをお勧めします。そのように、彼らはフォーム上のビジネスの世話をしなければなりません、そしてあなたはそれを検証し、カスタムボタンを使用してそれを却下することができます。アプリの他の部分と同じように見えるが、より制御しやすいナビゲーションバーを設計することもできます。
NavigationBars Right Buttonアイテムにアクセスして、そのセレクタープロパティを設定してみてください...ここに参照 IBarButtonItem reference があります。セレクタを作成して設定するカスタムUIBarButtonItemへのバー...
[戻る]ボタンをインターセプトするには、透明な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
これは https://stackoverflow.com/a/34343418/4316579 のkgaidisからの回答に基づいています
拡張機能の動作がいつ停止したかはわかりませんが、この記事の執筆時点(Swift 4)では、以下に説明するようにUINavigationBarDelegate準拠を宣言しない限り、拡張機能は実行されないようです。
これが、なぜ拡張機能が機能しなくなったのか疑問に思っている人々の助けになることを願っています。
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
}
}
少なくともXcode 5には、シンプルでかなり良い(完璧ではない)ソリューションがあります。 IBで、[ユーティリティ]ペインからバーボタンアイテムをドラッグし、[戻る]ボタンがあるナビゲーションバーの左側にドロップします。ラベルを「戻る」に設定します。 IBActionに結び付けてviewControllerを閉じることができる機能ボタンがあります。私はいくつかの作業を行ってから、巻き戻しセグエをトリガーし、完全に機能します。
理想的ではないのは、このボタンが<矢印を取得せず、以前のVCタイトルを引き継がないことですが、これは管理できると思います。私の目的のために、新しい戻るボタンを「完了」ボタンに設定し、その目的が明確になっています。
また、IBナビゲーターには2つの[戻る]ボタンが表示されますが、わかりやすくするためにラベルを付けるのは簡単です。
現在「nil」のままにしているターゲット変数とアクション変数を使用することで、ボタンが「選択」されたときに呼び出されるように保存ダイアログを配線できるはずです。気をつけて、これは奇妙な瞬間にトリガーされる可能性があります。
私はほとんどAmagrammerに同意しますが、矢印をカスタマイズしたボタンを作るのはそれほど難しいとは思いません。戻るボタンの名前を変更し、スクリーンショットを撮り、必要なボタンのサイズをPhotoshopで取り、それをボタンの上部の画像にします。
Swift
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
}
新しい方法が見つかりました:
- (void)didMoveToParentViewController:(UIViewController *)parent{
if (parent == NULL) {
NSLog(@"Back Pressed");
}
}
override func didMoveToParentViewController(parent: UIViewController?) {
if parent == nil {
println("Back Pressed")
}
}
私がこれまでに見つけた解決策はあまりいいものではありませんが、私にとってはうまくいきます。これを取ります answer 、プログラムでポップするかどうかもチェックします:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ((self.isMovingFromParentViewController || self.isBeingDismissed)
&& !self.isPoppingProgrammatically) {
// Do your stuff here
}
}
プログラムでポップする前に、そのプロパティをコントローラーに追加し、YESに設定する必要があります。
self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];
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)
})
])
}
}
@ 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
に準拠するだけで、この動作がデフォルトで採用されます。