【iOS】IBDesignableで画像をscaleAspectFitやscaleAspectFillで表示できるUIButtonを作る

はじめに

通信結果から画像を取得し、その画像をAspectFill(もしくはAspectFit)で設定して、その部分を押下すると詳細画面に遷移するというのはアプリ開発においてよくあるパターンだと思います。 例えば、メディア系のアプリでなんらかの特集をくみ、その特集の写真をタップすると詳細記事に遷移するといった場合です。

このような場合の一つの対応方法はUIViewの上にUIImageViewを貼り、その上に同じ大きさのUIButtonを載せるというものです。 ただ、このような実装は単純に手間なだけでなく、ボタン押下時の自然なアニメーション(全体に薄暗い反転がかかるもの)がなくなってしまいがちです。 本記事ではUIButtonの拡張クラスを用意し、UIButtonそのものに上記のような表示の機能を持たせる方法を記載します。

環境設定

以下の環境を使用しています。

  • Xcode10.2.1
  • Swift 5.0.1

方法

肝となる部分は、UIButtonの設定項目である、 contentHrozontalAlignmentcontentVierticalAlignmentです。 これらは、それぞれButton内部のコンテンツ(titleLabel、imageView)をUIButton内にどのように配置するかを決定します。 デフォルトではこれらの値はそれぞれ .center.centerになっており、画像のサイズ以上に拡大することはできません。 それぞれを.fill, .fillとすることでボタンのサイズに追随して画像サイズが決定するようになります。

枠いっぱいまで広がった際に、画像部分がどのように表示されるか(短い方に合わせてAspect比を保って画面いっぱい、 長い方に合わせてAspect比を保って画面いっぱい)はUIButtonimageView.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のカスタムクラスをご紹介しました。 ぜひ使ってみてください。

参考