Skip to content

Instantly share code, notes, and snippets.

@HonmaMasaru
Last active February 27, 2023 06:10
Show Gist options
  • Save HonmaMasaru/3360426c1e7c2fd09983acaeb9ac3665 to your computer and use it in GitHub Desktop.
Save HonmaMasaru/3360426c1e7c2fd09983acaeb9ac3665 to your computer and use it in GitHub Desktop.
YoutubeのミニプレイヤーのようなView
import UIKit
import PlaygroundSupport
final class MyViewController: UIViewController {
override func loadView() {
let view = UIView(frame: .init(x: 0, y: 0, width: 375, height: 667))
view.backgroundColor = .lightGray
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
// ビューの設置
let miniPlayer = MiniPlayerView(frame: .zero)
miniPlayer.backgroundColor = .systemTeal
view.addSubview(miniPlayer)
}
}
final class MiniPlayerView: UIView {
/// 最小の高さ
private let minHeight: CGFloat = 60
/// 最大下マージン
private let maxMargin: CGFloat = -50
/// 高さの範囲
private var heightRange: ClosedRange<CGFloat> = 60...1000
/// 下マージンの範囲
private var marginRange: ClosedRange<CGFloat> = -50...0
/// 親ビューの高さ
private var superviewHeight: CGFloat = 1000
/// 最大最小表示の閾値
private var threshold: CGFloat = 500
/// ジェスチャー時のY座標のバッファー
private var pointYBuffer: CGFloat = 0
/// 高さの制約
private var heightConstraint: NSLayoutConstraint!
/// 下マージンの制約
private var bottomConstraint: NSLayoutConstraint!
/// 初期化
override init(frame: CGRect) {
super.init(frame: frame)
setGestureRecognizer()
}
/// 初期化
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setGestureRecognizer()
}
/// addSubviewされた際
override func didMoveToSuperview() {
super.didMoveToSuperview()
setConstraints()
}
/// ジェスチャーの設定
private func setGestureRecognizer() {
let recognizer = UIPanGestureRecognizer(target: self, action: #selector(onPanGesture(_:)))
addGestureRecognizer(recognizer)
}
/// 制約の設定
private func setConstraints() {
guard let superview else { return }
// 各種初期化
heightRange = minHeight...superview.frame.height
marginRange = maxMargin...0
superviewHeight = superview.frame.height
threshold = superview.frame.height / 2
// 制約の設定
translatesAutoresizingMaskIntoConstraints = false
heightConstraint = heightAnchor.constraint(equalToConstant: minHeight)
bottomConstraint = bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: maxMargin)
NSLayoutConstraint.activate([heightConstraint, bottomConstraint,
leftAnchor.constraint(equalTo: superview.leftAnchor),
rightAnchor.constraint(equalTo: superview.rightAnchor)])
}
/// ジェスチャーのイベント
@objc func onPanGesture(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .changed: // ジェスチャー中
let pointY = gesture.translation(in: self).y
let offset = pointYBuffer - pointY
pointYBuffer = pointY
// 位置調整
bottomConstraint.constant = marginRange.clump(bottomConstraint.constant + offset * (abs(maxMargin) / superviewHeight)) // 遅らせる
heightConstraint.constant = heightRange.clump(heightConstraint.constant + offset)
case .ended, .cancelled, .failed: // ジェスチャー終了
// 終了時Viewの高さが閾値以上だったら最大表示
if heightConstraint.constant > threshold {
heightConstraint.constant = superviewHeight
bottomConstraint.constant = 0
} else {
heightConstraint.constant = minHeight
bottomConstraint.constant = maxMargin
}
UIView.animate(withDuration: 0.2) { self.superview?.layoutIfNeeded() }
pointYBuffer = 0
default:
break
}
}
}
// MARK: -
private extension ClosedRange {
/// 値を範囲内に丸める
/// - Parameter value: 値
/// - Returns: 丸めた値
func clump(_ value: Bound) -> Bound {
if value < lowerBound {
return lowerBound
} else if value > upperBound {
return upperBound
}
return value
}
}
PlaygroundPage.current.setLiveView(MyViewController())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment