1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-11-12

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

1) 一旦、カスタマイズするButtonはSwipeButtonとして、UIViewを継承することにする。
SwipeButton.swift
import UIKit
@IBDesignable class SwipeButton: UIView {

}
2) 次に、表示使用とするラベルと背景、移動Buttonの部品等を定義して置く。
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部品を格納するよう、初期化処理をする
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して以下の動作を実装する
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メソットについて、エクステンションして実装しておく
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する動作に対して、動画効果のメソットをエクステンションして実装しておく
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クラスを実装する
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して、以下の動作を実装する
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が受けた場合の処理を実装する。
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を指定する。
SwipeButton.swift
  override func layoutSubviews() {
        super.layoutSubviews()

        button.touchSize = self.frame.size
10) 最後、作られたSwipeButton部品を使用して見よう
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 にお任せください
信頼且つ満足できる製品を納品いたします

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?