デザイナーさんから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 にお任せください
信頼且つ満足できる製品を納品いたします