【iOS】UIButton.Configurationに置き換える際のボタン余白設定の注意点
はじめに
どうもこんにちは、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.left
とimageEdgeInsets
をどのメソッドに置き換えるかなんですよね〜
既存のtitleEdgeInsets
とimageEdgeInsets
の代わりとなるメソッドについて、公式ドキュメントや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 Forum・stack 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