はじめに
通信結果から画像を取得し、その画像をAspectFill(もしくはAspectFit)で設定して、その部分を押下すると詳細画面に遷移するというのはアプリ開発においてよくあるパターンだと思います。 例えば、メディア系のアプリでなんらかの特集をくみ、その特集の写真をタップすると詳細記事に遷移するといった場合です。
このような場合の一つの対応方法はUIView
の上にUIImageView
を貼り、その上に同じ大きさのUIButton
を載せるというものです。
ただ、このような実装は単純に手間なだけでなく、ボタン押下時の自然なアニメーション(全体に薄暗い反転がかかるもの)がなくなってしまいがちです。
本記事ではUIButton
の拡張クラスを用意し、UIButton
そのものに上記のような表示の機能を持たせる方法を記載します。
環境設定
以下の環境を使用しています。
- Xcode10.2.1
- Swift 5.0.1
方法
肝となる部分は、UIButton
の設定項目である、 contentHrozontalAlignment
とcontentVierticalAlignment
です。
これらは、それぞれButton内部のコンテンツ(titleLabel、imageView)をUIButton内にどのように配置するかを決定します。
デフォルトではこれらの値はそれぞれ .center
、 .center
になっており、画像のサイズ以上に拡大することはできません。
それぞれを.fill
, .fill
とすることでボタンのサイズに追随して画像サイズが決定するようになります。
枠いっぱいまで広がった際に、画像部分がどのように表示されるか(短い方に合わせてAspect比を保って画面いっぱい、
長い方に合わせてAspect比を保って画面いっぱい)はUIButton
のimageView.contentMode
を設定することで指定できます。
あとは通信取得した画像をUIButton
に設定すれば「はじめに」に記載した様なUIを実現できます。
※余談ですがURLから取得した画像の設定は例えばKingfisherでは button.kf.setImage(with: url, for: .normal)
のように実現できます。
import Foundation import UIKit @IBDesignable public final class ImageDesignableButton: UIButton { // MARK: - IBInspectable /// 画像部分のCornerRadius @IBInspectable public var imageCornerRadius: CGFloat { set { imageView?.layer.cornerRadius = newValue } get { return imageView?.layer.cornerRadius ?? 0.0 } } /// 画像部分のBorderWidth @IBInspectable public var imageBorderWidth: CGFloat { set { imageView?.layer.borderWidth = newValue } get { return imageView?.layer.borderWidth ?? 0.0 } } /// 画像部分のBorderColor @IBInspectable public var imageBorderColor: UIColor { set { imageView?.layer.borderColor = newValue.cgColor } get { return UIColor(cgColor: imageView?.layer.borderColor ?? UIColor.clear.cgColor) } } /// Button部分と画像部分のpadding @IBInspectable public var padding: CGFloat = 0 { didSet { // imageEdgeInsetsを設定してボタンの外枠と画像部分に隙間を作っています imageEdgeInsets = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding) } } // MARK: - Life Cycle public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit() } public override func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() commonInit() } public override init(frame: CGRect) { super.init(frame: frame) commonInit() } } // MARK: - Private Function private extension ImageDesignableButton { func commonInit() { // 画像部分の設定をしています // この部分はIBInspectableで外にだしてもいいかもしれません // image imageView?.contentMode = .scaleAspectFill // horizontal, verticalそれぞれの設定値を.fillとする // ことでコンテンツ部分いっぱいに画像が配置されるようになります contentHorizontalAlignment = .fill contentVerticalAlignment = .fill // 画像の外枠を丸くしたり、枠を作ったりする設定です // 必要に応じて使ってください // image layer imageView?.layer.borderColor = imageBorderColor.cgColor imageView?.layer.cornerRadius = imageCornerRadius imageView?.layer.borderWidth = imageBorderWidth } }
なお、paddingに関してtop, left, right, bottomと別々の値を使用したい場合は、privateでUIEdgeInsetsを定義して、 topPadding等をCGFloatで定義し、それぞれのPaddingからInsetsをいじることで似たように設定できます。
まとめ
Paddingをつけながら画像をaspectFillやaspectFitでボタンのサイズに追随して表示するUIButton
のカスタムクラスをご紹介しました。
ぜひ使ってみてください。
参考
- Apple Document - UIButton
- Apple Document - UIControl
- horizontalAlignment, verticalAlignmentに関する記載があります。