UISegmentedControlのセグメントとセグメントの間にビューを配置する

ios

UISegmentedControlの真ん中にボタンを置きたい

UISegmentedControlのセグメントとセグメントの間にボタンを配置する方法を調べてみました。
先に動作結果をご覧ください。

spacing_between_segments

検証環境

本記事は以下の環境で検証を行っています。

  • macOS Sierra バージョン 10.12.5
  • Xcode Version 8.3.3 (8E3004b)
  • iPhone 7シミュレータ iOS 10.3(14E8301)
  • Swift 3.1

ビューのレイアウト

最初にUISegmentedControlとUIButtonを持つカスタムビューを作ります。
今回はMyViewというクラスを作成しました。 クラスを作成したらxibで以下のようにレイアウトを行います。  

layout

UISegmentedControlは通常通り配置し、UIButtonも親ビューの中心に配置しているだけなので この時点ではUISegmentedControlとUIButtonは重なって表示されています。
これらのコンポーネントはIBOutletでソースコードと紐づけておきましょう。

コードでUISegmentedControlのセグメント間にスペースを空ける

レイアウトが出来たら、UISegmentedControlのセグメント間にボタンの幅分のスペースを空けてあげましょう。
UISegmentedControlにはsetDividerImageというメソッドがあるのでこれを使います。
このメソッドは区切り画像を設定するためのメソッドですが、透明な画像を指定することでスペースが空いているように見せることができます。
UIImageの生成にはiOS 10から使えるUIGraphicsImageRendererを使用しています。

ソースコード

import UIKit

/// UISegmentedControlとUIButtonを持つView
class MyView: UIView {

    @IBOutlet weak var segmentedControl: UISegmentedControl!
    @IBOutlet weak var button: UIButton!

    override func layoutSubviews() {
        super.layoutSubviews()

        // SegmentedControlにボタン幅分のスペースを空ける。
        let dividerImage = UIImage.image(withColor: .clear,
                                         size: button.bounds.size)
        segmentedControl.setDividerImage(dividerImage,
                                         forLeftSegmentState: .normal,
                                         rightSegmentState: .normal,
                                         barMetrics: .default)
    }
}

// MARK: - UIImage

extension UIImage {

    /// 色とサイズを指定してUIImageを生成する。
    ///
    /// - Parameters:
    ///   - color: 色
    ///   - size: サイズ
    /// - Returns: 生成したUIImage
    static func image(withColor color: UIColor, size: CGSize) -> UIImage {
        let renderer = UIGraphicsImageRenderer(size: size)
        return renderer.image { ctx in
            ctx.cgContext.setFillColor(color.cgColor)
            ctx.fill(CGRect(origin: .zero, size: size))
        }
    }
}

カスタムビューをnibから読み込んで表示する

ここまで出来たらあとはカスタムビューを読み込んで表示してあげるだけです。
以下ではViewControllerが管理しているビューの下部に固定の高さで表示しています。

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // nibからViewを読み込む
        let nib = UINib(nibName: String(describing: MyView.self), bundle: nil)
        let myView = nib.instantiate(withOwner: nil, options: nil).first as! MyView

        // Auto Layoutの制約を設定
        myView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(myView)
        view.leadingAnchor.constraint(equalTo: myView.leadingAnchor).isActive = true
        view.trailingAnchor.constraint(equalTo: myView.trailingAnchor).isActive = true
        view.bottomAnchor.constraint(equalTo: myView.bottomAnchor).isActive = true
        myView.heightAnchor.constraint(equalToConstant: 48).isActive = true
    }
}

おわりに

ちょっとしたテクニックを使ってUISegmentedControlのセグメントとセグメントの間にボタンを配置してみました。どなたかの参考になれば幸いです。

参考記事

[iOS 10] UIGraphicsImageRenderer について