🦔

【iOS】UIButton.Configurationに置き換える際のボタン余白設定の注意点

2024/12/11に公開

はじめに

どうもこんにちは、iOSバリバリ初心者のKyuleeです!
こういう技術系の記事を書くのは初めてなので、多少分かりづらいところもあるかもしれないですが、太平洋のような心で暖かく見ていただければと思います!
今回は、「UIButton.Configurationに移行する際のボタン余白設定の注意点」についての記事になります!
UIButton.Configuration の基本知識は知っている前提で作成したので、あらかじめご了承ください!UIButton.Configuration を用いた余白設定に着目した記事となるため、UIButton.Configurationの定義や基本的な書き方についてはWWDC21の動画Appleドキュメントを参考にしてください🙏
よろしくお願いします!!🙇

従来の UIButton の余白設定

iOS15以前から使っていた従来の UIButton の余白設定に関するプロパティは以下の3つになります。

  • contentEdgeInsets:タイトル・画像を含むボタン内のコンテンツ全体の周辺の余白を調整
  • imageEdgeInsets:ボタン内の画像の周辺の余白を調整
  • titleEdgeInsets:ボタンのタイトル周辺の余白を調整

上記のメソッドを用いた従来のButtonの定義方法は、下記のようになったはずです。以下、このボタンを checkButtonとします。

private lazy var checkButton: UIButton = {
    let button = UIButton()
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setTitle("チェックする", for: .normal)
    button.setTitle("選択済み", for: .selected)
    button.setTitleColor(UIColor.black, for: .normal)
    button.titleLabel?.font = .systemFont(ofSize: 12, weight: .semibold)
    button.setImage(UIImage(systemName: "checkmark.circle.fill")?.withTintColor(.systemGray3, renderingMode: .alwaysOriginal), for: .normal)
    button.setImage(UIImage(systemName: "checkmark.circle.fill")?.withTintColor(.systemGreen, renderingMode: .alwaysOriginal), for: .selected)
    button.contentHorizontalAlignment = .left
    button.titleEdgeInsets.left = 10
    button.imageEdgeInsets = .init(top: 5, left: 5, bottom: 5, right: 0)
    button.imageView?.contentMode = .scaleAspectFit
    button.addAction(.init { _ in
        self.checkButton.isSelected.toggle()
    }, for: .touchUpInside)

    return button
}()


既存の方法で作成したボタン

それでは、上記のボタンを次の章で UIButton.Configuration を用いて置き換えてみましょう!

UIButton.Configurationを用いた余白設定

iOS15(Xcode13)以降、 contentEdgeInsets, imageEdgeInsets, titleEdgeInsetsは非推奨(deprecated)となったため、これらが使えなくなる前に、WWDC21で新しく導入されたUIButton.Configurationへの置き換えが推奨されています。

UIButton.Configuration を用いた余白設定には、以下の3つのプロパティが使われます。

  • imagePadding:画像とボタンのタイトル(テキスト)間の余白を調整
  • titlePadding:タイトルとサブタイトル間の余白を調整
  • contentInsets:ボタン全体のコンテンツ(画像とタイトルを含む)の周囲に余白を調整。ボタンの外枠と、コンテンツの間にどれだけのスペースを置くかを指定。

上記のメソッドを用いて、checkButtonの余白設定を行おうと思います。

edgeInsetsメソッドで設定した数値をそのまま利用した場合

まず、checkButtonで定義したtitleEdgeInsets.leftimageEdgeInsetsをどのメソッドに置き換えるかなんですよね〜

既存のtitleEdgeInsetsimageEdgeInsetsの代わりとなるメソッドについて、公式ドキュメントやQiita、StackOverFlowなどの資料を調べてみましたが、結局似たような動作をしてくれるのは上記の imagePadding と ContentInsetsを適切に使うことでした。
titleEdgeInsets.leftはボタンの左側に配置した画像とタイトル間の余白を指しているため、imagePaddingで良さそうだし、画像周辺の余白を調整するimageEdgeInsetsはボタンのコンテンツを調整するしかないと考え、contentInsetsを利用することにしました。

上記を踏まえ、checkButtonのedgeInsets系のメソッドで設定した数値をそのまま利用したボタンを以下のように定義します。

// 定数を再利用するため、Enum化
private enum Const {
    static let normalCheckImage = UIImage(systemName: "checkmark.circle.fill")?.withTintColor(.systemGray3)
    static let selectedCheckImage = UIImage(systemName: "checkmark.circle.fill")?.withTintColor(.systemGreen.withAlphaComponent(0.7))
    static let normalCheckButtonTitle = "チェックボタン"
    static let selectedCheckButtonTitle = "選択済み"
    static let imageSize = CGSize(width: 20, height: 20)
}

private lazy var secondCheckButton: UIButton = {
    let button = UIButton()
    button.translatesAutoresizingMaskIntoConstraints = false
    var config = UIButton.Configuration.plain()
    config.title = Const.normalCheckButtonTitle
    config.image = Const.normalCheckImage?.withRenderingMode(.alwaysOriginal)
    config.imagePlacement = .leading
    config.imagePadding = 10
    config.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 0)
    config.baseBackgroundColor = .clear
    // ButtonのStateが変わってもfont と titleColorを保持させる
    config.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in
        var outgoing = incoming
        outgoing.foregroundColor = UIColor.black
        outgoing.font = .systemFont(ofSize: 12, weight: .semibold)
        return outgoing
    }

    button.contentHorizontalAlignment = .leading
    button.configuration = config
    button.addAction(.init { [weak self] _ in
        guard let self else { return }
        self.secondCheckButtonTapped()
    }, for: .touchUpInside)

    return button
}()

ビルドすると...?


うん...?😳

ボタン内の画像も拡大されているし、画像とタイトル間の余白も広かっている..?なんで??その理由について、公式ドキュメントDeveloper Forumstack overflowで調べたところ、明確な原因は得られなかったです。(もし、ご存知の方いらっしゃれば、コメントなどでご指摘いただけると!🙇)

画像のサイズについては、imageView?.contentMode など色々試してみましたが、Configurationを適用すると、contentModeが上手く適用されなかったです。どうやら、Configuration を用いると画像が元のサイズではなく、ボタン内で自動的に拡大・縮小されているような気がしますね🧐

また、imagePaddingが画像とタイトル間の余白に着目していることに対し、imageEdgeInsetsは画像周辺の余白を調整するため、プロパティで設定した数値が同じUIを作ることは難しいと考えられます。

我々の目的は、完全に同じUIに置き換えることなので...!、画像のサイズやボタン周辺の余白を再調整しましょう!

edgeInsetsメソッドで設定した数値を少し変えてConfigurationに適用した場合

画像とタイトル間の余白が広がっていたので、その数値を減らす方向で調整したボタンを以下のように定義します。

private lazy var thirdCheckButton: UIButton = {
    let button = UIButton()
    button.translatesAutoresizingMaskIntoConstraints = false

    var config = UIButton.Configuration.plain()
    config.title = Const.normalCheckButtonTitle
    config.baseBackgroundColor = .clear
    // UIButton.Configurationに書き換えた際、元のimageをそのまま使用すると修正前の画像サイズと一致しないため、画像サイズの再調整を行う
    config.image = resizeImage(with: Const.normalCheckImage, to: Const.imageSize)?.withRenderingMode(.alwaysOriginal)
    config.imagePlacement = .leading
    config.imagePadding = 5
    config.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 0)

    // ButtonのStateが変わってもfont と titleColorを設定の通りにする
    config.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in
        var outgoing = incoming
        outgoing.foregroundColor = UIColor.black
        outgoing.font = .systemFont(ofSize: 12, weight: .semibold)
        return outgoing
    }

    button.contentHorizontalAlignment = .leading
    button.configuration = config
    button.addAction(.init { [weak self] _ in
        guard let self else { return }
        self.thirdCheckButtonTapped()
    }, for: .touchUpInside)

    return button
}()

これでどうだ...!?

ビルド結果 View Hierarchy

ビルドした結果、シミュレータ上の表示とビューの階層(View Hierarchy)両方とも checkButtonと全く同じUIのボタンを作ることができました!☺️(View Hierarchyで1個目のボタンの画像表示が少しズレているように見えるのは内緒ではない..ご存知の方はコメントでご指摘オネシャス!🙇)

おわりに

UIButton.Configuration へ置き換えるとき、既存仕様のedgeInsets系のメソッドで設定した数値をそのまま利用するのは良くなさそうです...🥲
デザインを確認しつつ、都度数値を調整した方がより安全そうですね!☺️
UIButton.Configurationへの置き換えについて新たな発見があったら、今度また書きますね!
BYE BYE〜🤟!

Discussion