iphone - 表示されない - uinavigationcontroller 戻るボタン 画像




ナビゲーションコントローラの戻るボタンのアクションを設定する (19)

私は、ナビゲーションコントローラの戻るボタンのデフォルトアクションを上書きしようとしています。 私はカスタムボタンのアクションをターゲットにしました。 奇妙なことは、バックボタン属性を割り当ててもそれに注意を払わず、現在のビューをポップしてルートに戻るということです。

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

navigationItemのleftBarButtonItemを使って設定したら、私のアクションを呼び出しますが、ボタンは矢印付きのものではなく普通の丸いもののように見えます:

self.navigationItem.leftBarButtonItem = backButton;

ルートビューに戻る前にカスタムアクションを呼び出すにはどうすればよいですか? デフォルトのバックアクションを上書きする方法はありますか、またはビューを離れるときに常に呼び出されるメソッドがありますか(viewDidUnloadはそれをしません)?


Swift 4 iOS 11.3バージョン:

これはhttps://.com/a/34343418/4316579 kgaidisからの回答に基づいていhttps://.com/a/34343418/4316579

拡張機能が動作しなくなったときはわかりませんが、この記事(Swift 4)の時点では、以下で説明するようにUINavigationBarDelegate準拠を宣言しない限り、拡張機能はもう実行されません。

彼らの拡張機能がなぜ機能しなくなったのか不思議に思う人々に役立ちます。

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

    }
}

スウィフトバージョン:

( https://.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
    }
}


NavigationBars右ボタン項目にアクセスし、セレクタプロパティを設定することができます。参照をUIBarButtonItem参照で参照してください 。これが正常に動作することがわかっている場合は、ナビゲーションバーの右ボタン項目をカスタムUIBarButtonItemに設定しますセレクタを作成して設定してください...これが役に立ちます


onegrayの解決策は安全ではありません。アップルの公式文書https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.htmlに従うことで、これを避けるべきです。

カテゴリ内で宣言されたメソッドの名前が元のクラスのメソッドと同じ場合、または同じクラス(またはスーパークラス)上の別のカテゴリのメソッドと同じ場合、その動作はどのメソッド実装が使用されるかについては定義されていませんカテゴリを使用して標準のCocoaまたはCocoa Touchクラスにメソッドを追加するときに問題を引き起こす可能性があります。


UIViewController-BackButtonHandler拡張を実装しました。 何もサブクラス化する必要はなく、プロジェクトにUIViewControllerUIViewControllerクラスのnavigationShouldPopOnBackButtonメソッドをオーバーライドするだけです。

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

サンプルアプリケーションをダウンロードしてください


isMovingFromParentViewController使用する

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

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

@ 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準拠しています。この動作はデフォルトで採用されています。


いくつかのスレッディングの理由から、@ HansPinckaersに言及された解決策は私にとっては適切ではありませんでしたが、私は戻ってボタンを押さえるのがとても簡単な方法を見つけました。他の誰か。 そのトリックは本当に簡単です:透明なUIButtonをサブビューとしてUINavigationBarに追加し、あたかも実際のボタンのようにセレクタを設定してください! 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
    }
}

面白い事実:テスト目的と私の偽のボタンのための良い次元を見つけるために、私はその背景色を青に設定...そして、それはバックボタンの後ろに表示されます! とにかく、元のボタンをターゲットにしたタッチはまだ捕捉されます。


ここでは、Swift 3バージョンの@onewayの解答を捕捉するための解答ボタンのバックボタンイベントが発生する前です。 UINavigationBarDelegateを使用できないため、 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 falsereturn 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")       
}

このようなユーザー入力を必要とするフォームでは、ナビゲーションスタックの一部ではなく「モーダル」として呼び出すことをお勧めします。 そうすれば、フォーム上でビジネスを処理しなければならず、次にそれを検証してカスタムボタンを使用して却下することができます。 アプリケーションの残りの部分と同じように見えるナビゲーションバーを設計することもできますが、より多くの制御を提供します。


このアプローチは私のために働いた(しかし、 "戻る"ボタンは "<"記号を持たない)。

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

これをプレスを検出したいビューコントローラに入れてみてください:

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

それを行う新しい方法が見つかりました:

目標-C

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

迅速

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}

戻るボタンのスタイルも保持する解決策を見つけました。 ビューコントローラに次のメソッドを追加します。

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

バックボタンを透明なボタンで覆うだけです)。


戻るボタンを傍受するには、単純に透明な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

直接行うことはできません。 いくつかの選択肢があります:

  1. テストが合格するとタップとポップを検証する独自のカスタムUIBarButtonItemを作成します
  2. ReturnまたはDoneボタンがキーボードで押された後に呼び出される-textFieldShouldReturn:などのUITextFieldデリゲートメソッドを使用して、フォームフィールドの内容を検証Doneます

最初のオプションの欠点は、カスタムボタンのボタンから戻るボタンの左向き矢印スタイルにアクセスできないことです。 したがって、画像を使用するか、通常のスタイルのボタンを使用する必要があります。

2番目のオプションは、デリゲートメソッドでテキストフィールドを戻すためです。デリゲートコールバックメソッドに送信される特定のテキストフィールドに検証ロジックを指定できます。


私が今までに見つけた解決策はそれほど素晴らしいものではありませんが、それは私のために働きます。 この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];

迅速

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
}