こういうの。

alt

実装してみた順番的には案2が先になるが、結局案1の汎用性が高いので先にそちらを示す。

案1:「editingのTrue/Falseに合わせてボタンを描画/nil消去」作戦

    @IBOutlet weak var albumsTableView: UITableView!
    var cancelBarButtonItem: UIBarButtonItem!

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.rightBarButtonItem = editButtonItem // setEditing()とセット
        cancelBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelBarButtonTapped(_:))) // editingのときのみ表示
    }

    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)

        albumsTableView.setEditing(editing, animated: animated)

        if editing {
            navigationItem.leftBarButtonItem = cancelBarButtonItem
        } else {
            navigationItem.leftBarButtonItem = nil
        }
    }

    @objc func cancelBarButtonTapped(_ sender: UIBarButtonItem) {
        // ここにキャンセル時のアクションを実装

        setEditing(false, animated: true)
    }

解説

こうして必要最低限のみ書き出してみると本当に単純なことをしているな。UIBarButtonItemの変数cancelBarButtonItemを定義して、viewDidLoad()内部で初期化しておく。ちなみにvar定義部で初期化もしてしまうとactionが実行されなくなる。(参考:Xcode - UIBarButtonItemのactionメソッドが呼ばれないケースがある|teratail

setEditing()の説明は省略する。まあeditButtonItemと組み合わせたらNavigation BarにEdit←→Done切り替え機能ありのボタンが勝手に作られるだけ。引数のediting: Boolが、編集状態を表す真偽値であり、切り替わるたびにsetEditing()が呼ばれる。そこでif editing {} else {}文を実装すれば同じタイミングで切り替えられる。

あとは、メソッド名はなんでも良いが(よくねぇ)@objcでキャンセルアクションを定義して、最後にediting = falseとなるようにsetEditing(false, animated: true)で実行してやれば、if文でボタンがnilになる。

この実装の何が便利かってif文で一緒にnavigationItem.hidesBackButton = true/falseを実装すれば、編集モードではNavigation Bar左側の「<Back」ボタンが消えて「Cancel」ボタン、モード解除で入れ替わる、というのが簡単に実装できることである。

ただ、animated: trueとしているのに滑らかに変化しない。TableViewも編集モードになっているがそれがピチュンて元に戻る。とりあえず放置しているが。

案2:「無効かつ透明にしたら非表示と一緒」作戦

    @IBOutlet weak var albumsTableView: UITableView!
    @IBOutlet weak var cancelBarButtonItem: UIBarButtonItem!

    override func viewDidLoad() {
        super.viewDidLoad()
        cancelBarButtonItem.isEnabled = false // editingのときだけ有効にする
        cancelBarButtonItem.tintColor = .clear
    }

    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)

        albumsTableView.setEditing(editing, animated: animated)

        if editing {
            cancelBarButtonItem.isEnabled = true
            cancelBarButtonItem.tintColor = .systemBlue
        } else {
            cancelBarButtonItem.isEnabled = false
            cancelBarButtonItem.tintColor = .clear
        }
    }

    @IBAction func cancelBarButtonTapped(_ sender: Any) {
        // ここにキャンセル時のアクションを実装

        setEditing(false, animated: true)
    }

解説

作戦名の通りなので、案1の解説を先に読んでもらえればとくにこちらで補足することはない。あ、この場合は先に、Navigation Bar左側にボタンを配置して、cancelBarButtonItemcancelBarButtonTappedに接続させておく必要がある。

こちらもがんばれば「<Back」ボタンとの入れ替えができるのかもしれないが、案1よりシンプルではないだろう。しらんけど。

参考資料