xyk blog

最近は iOS 開発の記事が多めです。

SwiftでON・OFFの切り替えをする円形ボタンを作る

環境: Swift3

f:id:xyk:20170123143938g:plain

こんな感じの円形ボタンのカスタムビューを作る。
ボタンというよりUISwitch的なON・OFFの状態切り替えをさせたい。
UIControlを継承して、状態はisSelectedプロパティで保持している。

import UIKit
import PlaygroundSupport

final class CircleView: UIControl {

    var didTouchUpInsideHandler: (() -> Void)?
    
    let normalColor = UIColor(hex: 0x59acff)
    let selectedColor = UIColor(hex: 0xFF6E86)

    var circleColor: UIColor {
        return self.isSelected ? self.selectedColor : self.normalColor
    }

    // タッチの反応を円内のみとする
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        return self.circleShapeLayer.path?.contains(point) ?? false
    }

    // タッチ時、離れた時に呼ばれる
    override var isHighlighted: Bool {
        didSet {
            guard oldValue != self.isHighlighted else { return }
            
            self.circleShapeLayer.fillColor = self.isHighlighted ?
                self.circleColor.darkColor().cgColor : self.circleColor.cgColor

            if self.isHighlighted {
                UIView.animate(
                    withDuration: 0.05,
                    delay: 0,
                    options: [.allowUserInteraction, .beginFromCurrentState],
                    animations: { [weak self] in
                        self?.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
                })
            } else {
                UIView.animate(
                    withDuration: 1,
                    delay: 0,
                    usingSpringWithDamping: 0.15,
                    initialSpringVelocity: 10,
                    options: [.allowUserInteraction, .beginFromCurrentState],
                    animations: { [weak self] in
                        self?.transform = CGAffineTransform.identity
                })
            }
        }
    }
    
    // 円の描画
    override func layoutSublayers(of layer: CALayer) {
        super.layoutSublayers(of: layer)
        
        if self.circleShapeLayer.superlayer == nil {
            self.layer.insertSublayer(self.circleShapeLayer, at: 0)
        }
    }
    
    // 円のlayer作成
    lazy var circleShapeLayer: CAShapeLayer = { [unowned self] in
        
        let path = UIBezierPath(ovalIn: self.bounds)
        
        let shapeLayer = CAShapeLayer()
        shapeLayer.fillColor = self.circleColor.cgColor
        shapeLayer.path = path.cgPath
        
        return shapeLayer
    }()

    // タッチを離した時
    override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
        super.endTracking(touch, with: event)
        
        // 円内で離した場合のみに反応させる
        if let point = touch?.location(in: self),
            let path = self.circleShapeLayer.path,
            path.contains(point) {
            
            self.isSelected = !self.isSelected
            self.didTouchUpInsideHandler?()
        }
    }
}

extension UIColor {
    
    convenience init(hex: UInt32, alpha: CGFloat = 1.0) {
        let mask = 0x000000FF
        
        let r = Int(hex >> 16) & mask
        let g = Int(hex >> 8) & mask
        let b = Int(hex) & mask
        
        let red   = CGFloat(r) / 255
        let green = CGFloat(g) / 255
        let blue  = CGFloat(b) / 255
        
        self.init(red:red, green:green, blue:blue, alpha: alpha)
    }
    
    // 暗めの色にする
    func darkColor(brightnessRatio: CGFloat = 0.8) -> UIColor {
        
        var hue: CGFloat = 0
        var saturation: CGFloat = 0
        var brightness: CGFloat = 0
        var alpha: CGFloat = 0
        
        if self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
            return UIColor(hue: hue, saturation: saturation, brightness: brightness * brightnessRatio, alpha: alpha)
        } else {
            return self
        }
    }
}

let baseView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
baseView.backgroundColor = .white

let view = CircleView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.center = baseView.center
baseView.addSubview(view)

PlaygroundPage.current.liveView = baseView