iOS 自定义 仿苹果地图 半屏滑动效果控件

news2024/11/24 4:40:22

前言

在前一篇文章AI编程探索- iOS 实现类似苹果地图 App 中的半屏拉起效果我们通过三方库实现了这个功能。可是我发现这个三方不能加阴影效果。也许是我不知道怎么加吧!于是只有自己搞咯!

请添加图片描述

拆解功能

这功能给人在感觉上,有点麻烦,其实认真分析其中用到的技术点,你也会跟我一样发出灵魂拷问,就这?

  1. 圆角+阴影
  2. 平移手势
  3. 平移动画效果

圆角+阴影

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1953397.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

奇怪的Excel单元格字体颜色格式

使用VBA代码修改单元格全部字符字体颜色是个很简单的任务&#xff0c;例如设置A1单元格字体颜色为红色。 Range("A1").Font.Color RGB(255, 0, 0)有时需要修改部分字符的颜色&#xff0c;如下图所示&#xff0c;将红色字符字体颜色修改为蓝色。代码将会稍许复杂&am…

【MySQL进阶之路 | 高级篇】MVCC三剑客:隐藏字段,Undo Log,ReadView

1. 再谈隔离级别 我们知道事务有四个隔离级别&#xff0c;可能存在三种并发问题&#xff1a; 在MySQL中&#xff0c;默认的隔离级别是可重复读&#xff0c;可以解决脏读和不可重复读的问题&#xff0c;如果仅从定义的角度来看&#xff0c;它并不能解决幻读问题。如果我们想要解…

如何用find命令按文件大小快速查找并美化输出显示

背景 在系统中使用find命令查找大于20MB的文件非常简单&#xff0c;但默认情况下&#xff0c;输出结果中只显示文件路径&#xff0c;而不显示文件大小。如下图所示&#xff1a; 如果输出中能够同时显示文件大小&#xff0c;并且对内容进行适当的着色&#xff0c;这将显著提高其…

“论软件测试中缺陷管理及其应用”写作框架,软考高级论文,系统架构设计师论文

原创范文 软件缺陷指的是计算机软件或程序中存在的某种破坏正常运行能力的问题、错误&#xff0c;或者隐藏的功能缺陷。缺陷的存在会导致软件产品在某种程度上不能满足用户的需要。在目前的软件开发过程中&#xff0c;缺陷是不可避免的。软件测试是发现缺陷的主要手段&#xf…

【信创】udisk2服务异常导致U盘使用中自动移除问题解决

原文链接&#xff1a;【信创】udisk2服务异常导致U盘使用中自动移除问题解决 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在信创终端操作系统上由于udisk2服务异常导致U盘等移动设备在使用中自动移除问题的排查文章。udisk2是一个管理存储设备的服务&#xf…

全球性“微软蓝屏”事件及其对网络安全和系统稳定性的深远影响

近日&#xff0c;一次由微软视窗系统软件更新引发的全球性“微软蓝屏”事件&#xff0c;不仅成为科技领域的热点新闻&#xff0c;更是一次对全球IT基础设施韧性与安全性的深刻检验。这次事件源于美国电脑安全技术公司“众击”提供的一个带有“缺陷”的软件更新&#xff0c;它如…

2024年起重信号司索工(建筑特殊工种)证模拟考试题库及起重信号司索工(建筑特殊工种)理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年起重信号司索工(建筑特殊工种)证模拟考试题库及起重信号司索工(建筑特殊工种)理论考试试题是由安全生产模拟考试一点通提供&#xff0c;起重信号司索工(建筑特殊工种)证模拟考试题库是根据起重信号司索工(建筑特…

学习硬件测试02:系统框图讲解+时钟树讲解+标准程序框架详解(P47~P49)

一、系统框图讲解 二、时钟树讲解 三、标准程序框架详解

记录|博图中VB脚本和子程序之间的区别?

目录 前言一、VB脚本1. 基本认知2. Function类型中的参数3. 对比看Function和Sub4. 运行VB脚本 更新时间 前言 VB脚本函数与子程序的区别 看到博途中有个VB脚本&#xff0c;感觉和编程相关&#xff0c;想知道下VB脚本能干什么&#xff1f;能否解决生成日志等问题。 一、VB脚本 …

selenium----CSS表达式选择元素

前面我们学习了根据 id、class属性、tag名 选择元素。 如果我们要选择的 元素 没有id、class 属性&#xff0c;或者有些我们不想选择的元素 也有相同的 id、class属性值&#xff0c;怎么办呢&#xff1f;这时候我们通常可以通过 CSS selector 语法选择元素。 选择元素 通过 …

我澄清下,大数据界面虽然有点花,但对趋势的判断还是很准的!

我澄清下&#xff0c;大数据界面虽然有点花&#xff0c;但对趋势的判断还是很准的&#xff01; 艾斯视觉的观点认为&#xff1a;在这个充满不确定性的世界里&#xff0c;大数据就像一位智者&#xff0c;透过那些令人眼花缭乱的界面&#xff0c;总能以它独到的洞察力&#xff0…

大学计算机专业主要课程及概要介绍

大学计算机专业主要课程及概要介绍 大学计算机专业是一门涵盖广泛领域的学科&#xff0c;旨在培养学生在计算机科学与技术方面的理论知识与实践能力。该专业课程设置丰富多样&#xff0c;涵盖了从基础理论到高级应用的多个方面。以下是一些主要的课程及其概要介绍&#xff1a;…

C语言的周末小练习

周末小练习&#xff1a; 1.确认基础类型所占用的内存空间。 #include <stdio.h>int main() {printf("char: %u byte(s)\n", sizeof(char));printf("short: %u byte(s)\n", sizeof(short));printf("int: %u byte(s)\n", sizeof(int));pr…

【Maven学习】-1. 简介

文章目录 Maven学习1. 简介1.1 介绍1.2 安装1.2.1 下载1.2.2 安装maven1.2.3 配置镜像源(加快下载)1.2.4 IDEA配置maven 1.3 基于IDEA进行Maven工程构建Maven工程的GAVP创建Maven工程项目结构说明 1.4 Maven生命周期1.4.1 介绍1.4.2 命令讲解编译(mvn compile)删除(mvn clean)测…

26 Python序列结构

Python 中常用的序列结构有列表、元组、字典、字符串、集合等。 从是否有序这个角度看&#xff0c;Python 序列可以分为有序序列和无序序列&#xff1b;从是否可变来看&#xff0c;Python 序列可以分为可变序列和不可变序列两大类。 生成器对象和 range、map、enumerate、filte…

libtorch学习历程(1)环境搭建:VS+libtorch

1. 开发环境 Win11VS 2022 CommunityRTX4060pytorch 2.4.0cu121&#xff08;事先安装好&#xff09;libtorch 2.4.0cu121 release版opencv 4.10.0 2.软件下载 2.1 VS 直接去官网下载即可&#xff0c;最好是2017版本之后&#xff0c;需要选择C的桌面开发。 2.1 opencv 下…

嵌入式人工智能(30-基于树莓派4B的气体传感器-MQ系列烟雾、酒精、空气质量等传感器)

1、气体传感器 气体传感器是一种用于检测和测量空气中各种气体浓度的设备。它们通常使用化学反应、光学原理或电化学原理来检测气体&#xff0c;并通过输出电信号或其他输出形式来指示测量结果。 气体传感器在许多领域都有应用&#xff0c;包括工业安全、环境监测、室内空气质…

photoshop学习笔记——移动工具

移动工具&#xff0c;可以对图层进行移动&#xff0c;快捷键 V 使用的素材已经放上了&#xff0c;直接下载即可 按住ctrl 可以自动选取&#xff0c;鼠标点击哪个对象&#xff0c;自动选中哪个图层 按住 shift 校正角度&#xff08;只能沿着直线移动&#xff09; 按住 alt 拖…

第 1 章 预备知识

1、C简介 C继承了 C 语言高效、简洁、快速和可移植性的传统。C面向对象的特性带来了全新的编程方法&#xff0c;这种方法是为应付复杂程度不断提高的现代编程任务而设计的。 C的模板特性提供了另一种全新的编程方法——泛型编程。 C融合了 3 种不同的编程方式&#xff1a;C …

请你谈谈:spring bean的生命周期 - 阶段5:BeanPostProcessor前置处理-自定义初始化逻辑-BeanPostProcess后置处理

BeanPostProcessor的postProcessBeforeInitialization方法是在bean的依赖注入&#xff08;即属性填充&#xff09;完成后&#xff0c;但在bean的初始化回调&#xff08;如PostConstruct注解的方法或InitializingBean接口的afterPropertiesSet方法&#xff09;之前被调用的。 具…