Swipe風のiOS Buttonを作って見る(swift編)

デザイナーさんからSwipe風のスライド動作で実現できるiOSボタンを作って欲しい、
要望があって、カスタマイズしたUIButtonを実現して見た。

##### 1) 一旦、カスタマイズするButtonはSwipeButtonとして、UIViewを継承することにする。

```swift:SwipeButton.swift
import UIKit
@IBDesignable class SwipeButton: UIView {

}
```
##### 2) 次に、表示使用とするラベルと背景、移動Buttonの部品等を定義して置く。

```swift:SwipeButton.swift
import UIKit
@IBDesignable class SwipeButton: UIView {
    @IBInspectable var textColor : UIColor = UIColor.white
    @IBInspectable var frontColor : UIColor = UIColor.yellow
    @IBInspectable var groundColor : UIColor = UIColor.gray
    @IBInspectable var backColor : UIColor = UIColor.clear
    @IBInspectable var textFont : UIFont = UIFont.systemFont(ofSize: 16)

    public lazy var textLabel: UILabel = { [unowned self] in
        let label = UILabel()
        label.textColor = textColor
        label.backgroundColor = .clear
        label.numberOfLines = 1
        label.textAlignment = .center
        label.font = textFont
        return label
        }()
    
    public lazy var bar: UIView = { [unowned self] in
        let view = UIView()
        view.backgroundColor = groundColor
        return view
        }()
    
    public lazy var button: RoundView = { [unowned self] in
        let view = RoundView()
        view.backgroundColor = frontColor
        return view
        }()
```

##### 3) SwipeButtonが初期化する時の各SubView部品を格納するよう、初期化処理をする

```swift:SwipeButton.swift
import UIKit
@IBDesignable class SwipeButton: UIView {
    public var text: String? = nil //ヒントテキスト

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.commInt()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.commInt()
    }
    
    public convenience init(_ text: String?) {
        self.init()
        self.text = text
        self.commInt()
    }

    private func commInt() {
        self.backgroundColor = backColor
        
        self.addSubview(bar)
        self.bar.addSubview(textLabel)
        self.addSubview(button)
        self.bringSubviewToFront(button)
    }
```

##### 4) UIViewのlayoutSubviewsメソットをoverrideして以下の動作を実装する

```swift:SwipeButton.swift

    public var isRightToLeft: Bool = true //どの方向に移動するか定義
    public var isEnabled: Bool = true //ユーザが操作できるか定義
    private var startFrame:CGRect! //Buttonが移動する前のFrame情報
    private var turnedFrame:CGRect!//Buttonが移動した後のFrame情報
    @IBInspectable var barHeight: CGFloat = 40.0 //bar部品の高さ

    override func layoutSubviews() {
        super.layoutSubviews()
        
        let maxWidth: CGFloat = self.frame.width
        let maxHeight: CGFloat = self.frame.height
        if barHeight > maxHeight {
            barHeight = maxHeight
        }
        if barHeight < 10 {
            textLabel.isHidden = true
        }
        self.bar.frame = CGRect(x: 0, y: (maxHeight - barHeight) / 2, width: maxWidth, height: barHeight)
        if self.text != nil,!textLabel.isHidden {
            textLabel.text = self.text
            textLabel.frame = CGRect(origin: .zero, size: bar.frame.size)
        } else {
            textLabel.frame = CGRect.zero
        }
        let leftFrame = CGRect(x: 0, y: 0, width: maxHeight, height: maxHeight)
        let rightFrame  = CGRect(x: (maxWidth - maxHeight), y: 0, width: maxHeight, height: maxHeight)

        if isRightToLeft {
            self.startFrame = rightFrame
            self.turnedFrame = leftFrame
        } else {
            self.startFrame = leftFrame
            self.turnedFrame = rightFrame
        }
        button.frame = startFrame
        
        if isEnabled {
            button.isUserInteractionEnabled = true
        } else {
            button.isUserInteractionEnabled = false
        }
        self.corner(bar)
        self.corner(button)
        if borderWidth > 0 {
            self.bouder(bar, width: borderWidth, color: borderColor)
        }
```

##### 5) 上記のcornerメソットとbouderメソットについて、エクステンションして実装しておく

```swift:SwipeButton.swift
extension SwipeButton {
    func bouder(_ view: UIView, width: CGFloat, color: UIColor) {
        view.layer.masksToBounds = true
        view.layer.borderColor = color.cgColor
        view.layer.borderWidth = width
    }
    
    func corner(_ view: UIView, radius: CGFloat = 0) {
        view.layer.masksToBounds = true
        view.layer.cornerRadius = (radius > 0) ? radius : (view.frame.height / 2)
    }
}
```

##### 6) buttonがSwipeする動作に対して、動画効果のメソットをエクステンションして実装しておく

```swift:SwipeButton.swift
    // Swipeした後の部品色について、定義する
    @IBInspectable var swipedFrontColor : UIColor = UIColor.red
    @IBInspectable var swipedGroundColor : UIColor = UIColor.darkGray
    @IBInspectable var swipedTextColor : UIColor = UIColor.systemYellow
    //Swipeした後,表示しようとするテキストを定義
    public var swipedText: String?

extension SwipeButton {
    // 前に進む動画処理
    func swipeToGo() {
        UIView.animate(withDuration: self.duration, animations: {
            self.button.frame = self.turnedFrame
        }){ (completed) in
            self.button.backgroundColor = self.swipedFrontColor
            self.bar.backgroundColor = self.swipedGroundColor
            self.textLabel.textColor = self.swipedTextColor
            if let swipedText = self.swipedText {
                self.textLabel.text = swipedText
            }
        }
    }
    // 元に戻る動画処理
    func swipeToBack() {
        UIView.animate(withDuration: self.duration, animations: {
            self.button.frame = self.startFrame
        }){ (completed) in
            self.button.backgroundColor = self.frontColor
            self.bar.backgroundColor = self.groundColor
            self.textLabel.textColor = self.textColor
            if let text = self.text {
                self.textLabel.text = text
            } else {
                self.textLabel.text = nil
            }
        }
    }
}
```

##### 7) これからはSwipe移動するbuttonクラスを実装する

```swift:SwipeButton.swift

protocol RoundViewDelegate: class {
    // ButtongaタッチされたらDelegateプロトコルで通知するメソットを定義しておく
    func roundViewTouchesEnded() -> Void
}
class RoundView: UIView {
    weak var delegate: RoundViewDelegate?

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.isUserInteractionEnabled = true
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
```
##### 8) UIViewのタッチイベントを検知するtouchesBegan、touchesMoved、touchesEndedメソットをそれぞれ、overrideして、以下の動作を実装する

```swift:SwipeButton.swift

    weak var delegate: RoundViewDelegate?　// タッチイベントのDelegate
    public var touchSize: CGSize! // 移動範囲
    private var locationInitialTouch: CGPoint! //最初位置

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else{
            return
        }
        let location = touch.location(in: self)
        print(" Began:(\(location.x), \(location.y))")

        locationInitialTouch = location
    }
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else {
            return
        }
        let location = touch.location(in: self)
        if location.x < 0 || location.x > touchSize.width { return }
        if location.y < 0 || location.y > touchSize.height { return }
        print(" Moved:(\(location.x), \(location.y))")
        let f = frame.offsetBy(dx: location.x - locationInitialTouch.x, dy: 0)
        if (f.minX >= 0 && f.maxX <= touchSize.width) {
            frame = f
        }
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        defer {
            delegate?.roundViewTouchesEnded()
        }
        guard let touch = touches.first else{
            return
        }
        let location = touch.location(in: self)
        if location.x < 0 || location.x > touchSize.width {
            return
        }
        if location.y < 0 || location.y > touchSize.height {
            return
        }
        print(" Ended:(\(location.x), \(location.y))")
        let f = frame.offsetBy(dx: location.x - locationInitialTouch.x, dy: 0)
        if (f.minX >= 0 && f.maxX <= touchSize.width) {
            frame = f
        }
    }

```

##### 9) SwipeButtonクラスがRoundViewDelegateが受けた場合の処理を実装する。

```swift:SwipeButton.swift
// MARK: - RoundViewDelegate
extension SwipeButton : RoundViewDelegate{
    func roundViewTouchesEnded() {
        if self.button.center.x > (self.frame.width / 2) {
            if self.isRightToLeft {
                swipeToBack()
            } else {
                swipeToGo()
            }
        } else {
            if self.isRightToLeft {
                swipeToGo()
            } else {
                swipeToBack()
            }
        }
    }
}
```

##### 10) SwipeButtonのlayoutSubviewsメソットにbuttonの移動範囲touchSizeを指定する。

```swift:SwipeButton.swift
  override func layoutSubviews() {
        super.layoutSubviews()

        button.touchSize = self.frame.size
```

##### 10) 最後、作られたSwipeButton部品を使用して見よう
```swift:SwipeButton.swift
class ViewController: UIViewController {
    var swipeButton : SwipeButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupSwipeButton()
       }

    func setupSwipeButton() {
        if swipeButton == nil {
            self.swipeButton = SwipeButton(frame: CGRect(x:40, y:200, width: 300,height:50))
            swipeButton.isRightToLeft = true
            swipeButton.text = "すぐ買う"
            swipeButton.swipedText = "購入いたしました"
            self.view.addSubview(self.swipeButton)
        }
    }
```
Swipe後の動作処理については、この文対象以外で一旦、割愛させていください。

上記内容のソースは以下のURLに格納させて頂きます。
https://github.com/hq7781/SwipeButton/

以上、簡単な説明となりますが、乱文で失礼致します。


>iOS、Androidアプリの制作なら、https://origon.co.jp にお任せください
>信頼且つ満足できる製品を納品いたします