一个基于viewcontroll
12 March 2024
一个基于ViewController addChild方式实现的通用 EmbedPanelView
import Foundation
import UIKit
class EmbedPanelView: UIView {
private var defaultHeight = UIScreen.main.bounds.height * 0.618
//private let maxHeight = UIScreen.main.bounds.height * (1.0 - pow((1.0 - 0.618), 2))
private let maxHeight = UIScreen.main.bounds.height - 54.0
var getParentViewController: (() -> LiveRoomBaseViewController?)?
var getChildViewController: (() -> UIViewController?)?
var expandHandler: ((_ isExpand: Bool) -> Void)?
private var subViewsConfiged: Bool = false
private weak var childViewController: UIViewController?
private lazy var backgroundControl: UIControl = {
let control = UIControl(frame: .zero)
control.backgroundColor = .clear
control.addTarget(self, action: #selector(dismissEmbedVC), for: .touchUpInside)
return control
} ()
private lazy var containerView: UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = .white
view.layer.cornerRadius = 16.pt
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
view.clipsToBounds = true
return view
} ()
private lazy var headerView: UIView = {
let view = UIView(frame: .zero)
view.clipsToBounds = true
return view
}()
private lazy var contentView: UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = .clear
view.clipsToBounds = true
return view
}()
//MARK: default header subviews
private lazy var titleLabel: UILabel = {
let label = UILabel(frame: .zero)
label.backgroundColor = .white
label.textColor = UIColor.darkText
label.font = UIFont.systemFont(ofSize: 16.0, weight: .bold)
label.numberOfLines = 1
label.lineBreakMode = .byTruncatingTail
label.textAlignment = .left
label.text = "라이브 안내사항".localized
return label
} ()
private lazy var closeButton: UIButton = {
let button = UIButton(type: .custom)
button.backgroundColor = .white
button.setImage(UIImage.live.imageAsset(named: "iconClose2Outline20"), for: .normal)
button.addTarget(self, action: #selector(dismissEmbedVC), for: .touchUpInside)
return button
} ()
private lazy var upperDivider: RDSDivider = {
let divider = RDSDivider(type: .horizontal, size: .small)
divider.tintColor = UIColor.clear
divider.backgroundColor = UIColor.clear
return divider
}()
//MARK: - method
//required init?(coder aDecoder: NSCoder) {
// super.init(coder: aDecoder)
//}
init(enableExpand: Bool = false) {
super.init(frame: .zero)
if enableExpand {
containerView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))))
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func setupSubviews(customHeaderHandler:((_ headerView: UIView) -> Void)? = nil){
guard !self.subViewsConfiged else { return }
self.subViewsConfiged = true
self.configBasePanelView()
if let headerHandler = customHeaderHandler {
headerHandler(self.headerView)
}else { //default
self.headerView.addSubview(self.titleLabel)
self.headerView.addSubview(self.closeButton)
self.headerView.addSubview(self.upperDivider)
self.titleLabel.snp.makeConstraints { (make) in
make.leading.equalToSuperview().offset(16.pt)
make.top.equalToSuperview().offset(16.pt)
}
self.closeButton.snp.makeConstraints { (make) in
make.size.equalTo(20.pt)
make.trailing.equalToSuperview().offset(-16.pt)
make.centerY.equalTo(self.titleLabel)
}
self.upperDivider.snp.makeConstraints { (make) in
make.leading.trailing.equalToSuperview()
make.top.equalToSuperview().offset((44-1).pt)
}
}
}
private func configBasePanelView() {
self.backgroundColor = UIColor.black.withAlphaComponent(0.5)
self.addSubview(self.backgroundControl)
self.addSubview(self.containerView)
self.containerView.addSubview(self.headerView)
self.containerView.addSubview(self.contentView)
self.backgroundControl.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
self.containerView.snp.makeConstraints { (make) in
make.leading.trailing.equalToSuperview()
make.top.bottom.equalTo(self.snp.bottom)
}
self.headerView.snp.makeConstraints { (make) in
make.top.leading.trailing.equalToSuperview()
make.height.equalTo(44.pt)
}
self.contentView.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
make.top.equalTo(self.headerView.snp.bottom).offset(1.0)
}
}
func showEmbedVC(customHeaderHandler:((_ headerView: UIView) -> Void)? = nil) {
guard let parentVC = self.getParentViewController?(),
let childVC = self.getChildViewController?() else { return }
self.childViewController = childVC
//add panelView (self)
parentVC.view.addSubview(self)
self.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
self.setupSubviews(customHeaderHandler: customHeaderHandler)
//add child viewController
parentVC.addChild(childVC)
childVC.willMove(toParent: parentVC)
self.contentView.addSubview(childVC.view)
childVC.view.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
childVC.didMove(toParent: parentVC)
parentVC.view.layoutIfNeeded()
UIView.animate(withDuration: 0.25) { [weak self] in
guard let self = self else { return }
self.containerView.snp.remakeConstraints { (make) in
make.leading.trailing.bottom.equalToSuperview()
make.height.equalTo(self.defaultHeight)
}
self.layoutIfNeeded()
} completion: { finished in
if finished { /* do nothing */ }
}
self.getParentViewController?()?.roomService.autoShowBottomSheetInterrupt.popupInterrupt(for: self)
}
@objc func dismissEmbedVC() {
UIView.animate(withDuration: 0.25) { [weak self] in
guard let self = self else { return }
self.containerView.snp.remakeConstraints { (make) in
make.leading.trailing.equalToSuperview()
make.top.bottom.equalTo(self.snp.bottom)
}
self.layoutIfNeeded()
} completion: { [weak self] _ in
guard let self = self else { return }
if let childVC = self.childViewController,
childVC.parent != nil {
childVC.willMove(toParent: nil)
childVC.view.removeFromSuperview()
childVC.removeFromParent()
childVC.didMove(toParent: nil)
}
self.getParentViewController?()?.roomService.autoShowBottomSheetInterrupt.popupInterruptCancel(for: self)
self.removeFromSuperview()
}
}
private var startPointY: CGFloat = 0.0
private var startHeight: CGFloat = 0.0
@objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
let translation = gestureRecognizer.translation(in: self)
let velocity = gestureRecognizer.velocity(in: self)
switch gestureRecognizer.state {
case .began:
startPointY = translation.y
startHeight = containerView.frame.height
case .changed:
var newHeight = startHeight - translation.y
newHeight = max(defaultHeight, newHeight)
newHeight = min(maxHeight, newHeight)
containerView.snp.updateConstraints { make in
make.height.equalTo(newHeight)
}
case .ended, .cancelled, .failed:
let finalHeight = containerView.frame.height
if finalHeight > maxHeight - 200 && velocity.y < 0 {
showMaxHeight(animated: true)
} else if finalHeight < defaultHeight + 200 && velocity.y > 0 {
showDefaultHeight(animated: true)
}
default:
break
}
}
func showDefaultHeight(animated: Bool) {
containerView.snp.updateConstraints { make in
make.height.equalTo(defaultHeight)
}
if animated {
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
} completion: { [weak self] _ in
self?.expandHandler?(false)
}
} else {
layoutIfNeeded()
self.expandHandler?(false)
}
}
func showMaxHeight(animated: Bool, completion: ((_ isDefaultHeight: Bool) -> Void)? = nil) {
containerView.snp.updateConstraints { make in
make.height.equalTo(maxHeight)
}
if animated {
UIView.animate(withDuration: 0.3) {[weak self] in
self?.layoutIfNeeded()
} completion: { [weak self] _ in
self?.expandHandler?(true)
}
} else {
layoutIfNeeded()
self.expandHandler?(true)
}
}
}
