Last active
February 27, 2023 06:10
-
-
Save HonmaMasaru/3360426c1e7c2fd09983acaeb9ac3665 to your computer and use it in GitHub Desktop.
YoutubeのミニプレイヤーのようなView
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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