前言
在前一篇文章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思无邪愿与您一起在技术的道路上不断探索。如果您喜欢这篇文章,不妨留下您宝贵的赞!如果您对文章有任何疑问或建议,欢迎在评论区留言,我会第一时间处理,您的支持是我前行的动力,愿我们都能成为更好的自己!



















