前言
在前一篇文章AI编程探索- iOS 实现类似苹果地图 App 中的半屏拉起效果我们通过三方库实现了这个功能。可是我发现这个三方不能加阴影效果。也许是我不知道怎么加吧!于是只有自己搞咯!
拆解功能
这功能给人在感觉上,有点麻烦,其实认真分析其中用到的技术点,你也会跟我一样发出灵魂拷问,就这?
- 圆角+阴影
- 平移手势
- 平移动画效果
圆角+阴影
- 这功能已经老生常谈了,我还是在用比较古老的蠢办法。一个 View 加阴影,另一个View 加圆角。
平移手势
- 实现上下拖拉响应区域,控件跟随手势上下移动的效果。
- 添加平移手势
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
touchView.addGestureRecognizer(panGesture)
- 手势处理
@objc func handlePan(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: self.view)
switch gesture.state {
case .began, .changed:
// 更新约束并添加动画效果
case .ended, .cancelled, .failed:
// 手势结束、取消或失败时重置平移量
gesture.setTranslation(.zero, in: self.view)
default:
break
}
}
平移动画效果
- 更新约束
- 使用UIView 动画 + layoutIfNeeded 刷新布局 实现动画
- 示例
self.snp.updateConstraints { make in
make.bottom.equalToSuperview().offset(tempY)
}
UIView.animate(withDuration: 0.5) {
self.superview?.layoutIfNeeded()
}
完整实现
SheetPresentView
import UIKit
class SheetPresentView: UIView {
enum DragDirection {
case up
case down
}
var isExpand:Bool = true
///Y 轴 可移动的最大值
///控制收起时的显示高度 内容高 - transMax_Y = 收起时高度
var transMax_Y:CGFloat = 440
private var currentTrans_Y:CGFloat = 0
private var direction:DragDirection = .down
private var oldChangeY:CGFloat = 0
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
// 添加平移手势识别器
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
touchView.addGestureRecognizer(panGesture)
}
///设置 默认是否展开
func setDefaultIsExpand(isExpand:Bool){
self.isExpand = isExpand
if isExpand {
self.snp.updateConstraints { make in
make.bottom.equalToSuperview()
}
direction = .down
}else {
self.snp.updateConstraints { make in
make.bottom.equalToSuperview().offset(transMax_Y)
}
direction = .up
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.addShadow(shadowColor: .lightGray, shadowOffset: CGSize(width: 0, height: -3), shadowOpacity: 0.5, shadowRadius: 5)
contentView.addCorner(conrners: [.topLeft,.topRight], radius: 15)
}
@objc func handlePan(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: self.superview)
print("translationY:\(translation.y)")
switch gesture.state {
case .began, .changed:
// 更新约束并添加动画效果
var tempY = translation.y
//方向
if oldChangeY < translation.y {
direction = .down
}else {
direction = .up
}
//展开
if isExpand {
if tempY <= 0 {
tempY = 0
}
if tempY >= transMax_Y {
tempY = transMax_Y
}
}
//收起
if isExpand == false{
if tempY > 0 {
tempY = transMax_Y
}else {
tempY = transMax_Y + translation.y
if tempY <= 0 {
tempY = 0
}
}
}
oldChangeY = translation.y
self.snp.updateConstraints { make in
make.bottom.equalToSuperview().offset(tempY)
}
UIView.animate(withDuration: 0.1) {
self.superview?.layoutIfNeeded()
}
case .ended, .cancelled, .failed:
// 手势结束、取消或失败时重置平移量
gesture.setTranslation(.zero, in: self.superview)
updateViewPosition()
default:
break
}
}
private func updateViewPosition() {
var tempY:CGFloat = 0
if direction == .down {
tempY = transMax_Y
}
isExpand = direction == .up
self.snp.updateConstraints { make in
make.bottom.equalToSuperview().offset(tempY)
}
UIView.animate(withDuration: 0.3) {
self.superview?.layoutIfNeeded()
}
}
//抽屉 动画显示
func drawerShow(){
let tempY:CGFloat = 0
direction = .up
isExpand = direction == .up
self.snp.updateConstraints { make in
make.bottom.equalToSuperview().offset(tempY)
}
UIView.animate(withDuration: 0.5) {
self.superview?.layoutIfNeeded()
}
}
func setupUI(){
contentView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
touchView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
make.height.equalTo(scaleHeight(30))
}
touchLineV.snp.makeConstraints { make in
make.top.equalToSuperview().offset(scaleHeight(10))
make.centerX.equalToSuperview()
make.size.equalTo(CGSize(width: scaleHeight(52), height: scaleHeight(5)))
}
cutLineV.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(0.5)
}
}
lazy var contentView: UIView = {
let view = UIView()
view.backgroundColor = .white
self.addSubview(view)
return view
}()
lazy var touchView: UIView = {
let view = UIView()
view.backgroundColor = .white
contentView.addSubview(view)
return view
}()
lazy var touchLineV: UIView = {
let view = UIView()
view.backgroundColor = .gray
view.yh_cornerRadius = scaleHeight(3)
touchView.addSubview(view)
return view
}()
lazy var cutLineV: UIView = {
let view = UIView()
view.backgroundColor = .lightGray
touchView.addSubview(view)
return view
}()
}
使用示例
总结
这功能用到的技术点不难,难就难在计算滑动位置,滑到一半,应该往上回弹还是往下回弹。所以自定义这个控件还是必要,一次写好,后面就根据具体需求调整下样式就行。将这个控件当作模板,可以快速的定制实现类似需求的功能。这个控件不做任何样式的限制,想怎么改都可以。
感谢您的阅读和参与,HH思无邪愿与您一起在技术的道路上不断探索。如果您喜欢这篇文章,不妨留下您宝贵的赞!如果您对文章有任何疑问或建议,欢迎在评论区留言,我会第一时间处理,您的支持是我前行的动力,愿我们都能成为更好的自己!