32220


お世話になります。  
iOSのアプリをSwift3とXcode8.2.1で作っています。

## やりたいこと

UINavigationControllerの下でshowセグエで画面1と画面2を結びました。  
画面2に自動で作られた戻るボタンをタップした時に何か判定を入れて画面1に戻ることをキャンセルしたいです。  
また戻るアイコンを揃えたいので自動で作られる戻るボタンを使いたいです。

## 試したこと

画面2で`navigationItem.backBarButtonItem`を取得してactionを追加しようとしたのですが取得出来ませんでした。

ここで`navigationItem.backBarButtonItem`は遷移先のボタンのことなのだと思って画面1の方で以下のようにactionを設定したのですがtestは呼び出されませんでした。

[code]

    // 画面1のコード
    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIBarButtonItem(title: "戻るボタン", style: .plain, target: self, action: #selector(ViewController1.test))
        self.navigationItem.backBarButtonItem = button
    }
    
    func test() {
        print("画面1で追加されました。")
    }
    
[/code]

できれば画面2内に関係するコードを収めておきたいです。  
どうしたらいいでしょうか。よろしくお願いします。

### 追記: iOSのものと似た画像を使い戻るボタン自体を作る方法も試してみました。

[ここを参考](https://stackoverflow.com/questions/31717315/perform-segue-on-press-
on-back-
button/31717425#31717425)にして戻るボタンを作る方法も試してみたのですが調整項目が多くなんだか泥沼へ進んでいる気がします。

[code]

    // 画面2のコード    
    override func viewDidLoad() {
    
        super.viewDidLoad()
    
        guard let controllers = self.navigationController?.viewControllers else {
            return
        }
    
        let backButton = UIButton(type: .system)
        backButton.sizeToFit()
        backButton.setImage(#imageLiteral(resourceName: "戻るアイコンに似た画像"), for: .normal)
        backButton.titleEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 0)
        backButton.setTitle(controllers[controllers.count - 2].navigationItem.title, for: .normal)
        backButton.titleLabel?.font = UIFont.systemFont(ofSize: 17)
        backButton.addTarget(self, action: #selector(ViewController2.test), for: .touchUpInside)
    
        let barButton = UIBarButtonItem(customView: backButton)
        barButton.width = 0
    
        let spacer = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
        spacer.width = -18
    
        self.navigationItem.leftBarButtonItems = [spacer, barButton]
    
    }
    
[/code]

一つ前の`ViewController`の`navigationItem`についている`title`を戻るボタンに設定しているのですが、その文字列の長さによって戻るボタンの見た目が大きく変わってしまいます。


CC BY-SA 3.0
2017-01-22T06:39:05.477
32052
2017-01-28T11:48:59.597
2017-05-23T12:38:55.307
-1
14222
question
swift ios uinavigationcontroller
UINavigationControllerを使って作られる初期の戻るボタンにactionをつけたい


## カスタムボタンを使う方法

戻る時に判定を入れるためにカスタムボタンを作る方法を採用しました。  
使う画像によって設定は変わってきます。

[この方の記事](http://himaratsu.hatenablog.com/entry/ios/customview)のxibでできたViewをSubViewとして設定するコードを参考にxib名を引数に取るUIViewの拡張メソッド（ここではconfigureNib(nibName:
String)とします）を用意しました。

次にUIBarButtonItem内に配置するCustomViewを作ります。  
BackButtonCustomViewという名前でxibとUIViewのサブクラスを作ります。

BackButtonCustomViewはこんな感じにして

[code]

    // ストーリーボードでも配置できるがFixedSpaceBarButtonItemがLeftBarButtonItems内に配置できない？
    // のでコードから配置することのが多いかも知れない
    @IBDesignable
    class BackButtonCustomView: UIView {
    
        @IBOutlet var button: UIButton!
    
        // ボタンのラベルを変えたいときはbutton.setTitleではなくこっちを変える
        // actionは公開されているbuttonで設定するのにタイトルは専用のプロパティーでやっているのが気持ち悪い
        @IBInspectable
        var buttonTitle: String = "Button" {
            didSet {
                button.setTitle(self.buttonTitle, for: .normal)
                self.configure()
            }
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            self.configureNib(nibName: self.nibName)
            self.configure()
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.configureNib(nibName: self.nibName)
            self.configure()
        }
    
        private let nibName: String = "BackButtonCustomView"
    
        private func configure() {
            self.button.sizeToFit()
            // xibで調整した分を足す
            self.button.bounds.size.width += self.button.titleEdgeInsets.left
            self.button.bounds.size.height = 44
            // ボタンを包んでいるViewの大きさを変える
            self.bounds.size.width = self.button.bounds.width
            self.bounds.size.height = self.button.bounds.height
        }
    
    }
    
[/code]

xibはUIButtonを配置し画像やEdgeInsetsを設定しておきます。  
File's ownerを上のUIViewのサブクラスにします。

利用側のコード（質問でいう画面2）ではコードやstoryboardで配置したbarbuttonitem内のカスタムビュー内のボタンに戻るが押された時に使いたいactionを設定します。

コードからの場合

[code]

    override func viewDidLoad() {
            super.viewDidLoad()
            self.setUpBackButton()
    }
    
    private func setUpBackButton() {
            guard let controllers = self.navigationController?.viewControllers else {
                return
            }
            // 前のViewControllerのnavigationItemのタイトルをボタンに設定
            let customView = BackButtonCustomView(frame: CGRect.zero)
            customView.buttonTitle = controllers[controllers.count - 2].navigationItem.title!
            customView.button.addTarget(self, action: #selector(画面2.exit), for: .touchUpInside)
            let backButton = UIBarButtonItem(customView: customView)
            // 戻るボタンが右過ぎるので調整用のBarButtonItem
            let spacer = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
            spacer.width = -12
            self.navigationItem.leftBarButtonItems = [spacer, backButton]
    }
    
[/code]

storyboardからの場合

[code]

    @IBOutlet weak var customButton: UIBarButtonItem!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        (self.customButton.customView as! BackButtonCustomView).button
            .addTarget(self, action: #selector(画面2.exit), for: .touchUpInside)
    }
    
[/code]

BarButtonItemの位置調整をすることになるのですがこれをボタン側でやろうとすると（contentInsetでマイナスを入れていくなどすると）タップできる範囲がずれてしまいます。  
そこでFlexedSpaceBarButtonItemを調整用に置く方法で行こうと思いました。

標準の戻るボタンのデザインの更新の際に一箇所（ボタン側）だけ修正するようにしたいのですがナビゲーションバーのサブクラスを作ったりしないといけなさそうで戻る時に判定を入れるということよりも興味が広がってしまうのでは？と思いこうしました。  
UIBarButtonItemのサブクラスを作ってプロパティーを経由して中のUIButtonにactionやタイトルを設定するというのもいいかもしれませんが抽象的？になりすぎている気がしたのでやめました。

もっと綺麗に正しく書く方法があると思うのでそのときは何方かよろしくお願いします。


CC BY-SA 3.0
2017-01-28T11:48:59.597
32220
2017-01-28T11:48:59.597
14222
32052
answer