Swift 3.0 商城开发 —— 商城上拉弹出层(仿淘宝)

写在前面

在做项目的过程中,偶尔会转牛角尖,比如感觉很喜欢 京东 淘宝 的上拉弹出层获取商品属性的效果,于是就各种寻求思路,最终实现并封装成自己的类库,LWPopupViewController。将需要使用弹出层的 UIViewController 继承 LwPopupViewController 即可。简单易用。

效果图

我的项目效果图

image
image

设计思路

其实看起来无处下手的功能,分析起来很简单。

组件

  1. 首先此功能继承于一个 ViewController,在其中定义两个子视图(maskView 和 popView)
  2. maskView 是这盖层视图,popView 是弹出层视图。

原理

弹出过程:整个 ViewController 被缩放形成缩小状态(具体动画后续讲解);将 maskView 添加到 主视图(UIApplication.shared.keyWindow?)中,形成遮挡层。popView 添加到 主视图(UIApplication.shared.keyWindow?)中并动态修改 frame 形成弹出动画效果。

弹回过程:popView 通过修改 frame 在主视图中隐藏后,隐藏 maskView,同时 主 ViewController 从缩放状态回复到正常状态,最后将 maskView 和 popView 从应用主视图中移除。

具体实现

定义枚举类型

巧用枚举类型,将很大提高代码的逻辑性

// 主视图缩放 步骤
enum LWAnimateType {
    case first
    case second
}

// 弹出层操作事件
enum LWActionType {
    case popUp
    case popDown
}

定义控制器以及常规属性

class LWPopController: UIViewController {
    
    // 主视图控制器——视图
    var rootView = UIView()
    // 遮挡层
    var maskView: UIView!
    // 弹出层视图
    var popView:UIView!
    
    // 弹出层的高度 默认:400
    var popViewHeight:CGFloat = SCREEN_HEIGHT * 4 / 5
    
    // 动画周期
    var duration: TimeInterval = 0.3

    // 具体实现代码 在下面
}

初始化 maskView 和 popView

两个核心组件

override func viewDidLoad() {
    super.viewDidLoad()
    if let selfNV = self.navigationController {
        rootView = selfNV.view
    } else {
        rootView = self.view
    }
    // 定义尺寸
    maskView = UIView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: SCREEN_HEIGHT))
    // 定义背景色
    maskView.backgroundColor = UIColor.black
    // 定义透明度
    maskView.alpha = 0.2
    // 添加点击事件
    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(popDown))
    maskView.addGestureRecognizer(tapGesture)
    // 确保视图不被当前 UIView 视图遮挡
    maskView.layer.zPosition = CGFloat(INT8_MAX)
    // 定义尺寸
    popView = UIView(frame: CGRect(x: 0, y: SCREEN_HEIGHT, width: SCREEN_WIDTH, height: popViewHeight))
    /// 定义背景色
    popView.backgroundColor = UIColor.white
    /// 加个阴影
    popView.layer.shadowColor = UIColor.black.cgColor
    popView.layer.shadowOffset = CGSize(width: 0.5, height: 0.5)
    popView.layer.shadowOpacity = 0.8
    popView.layer.shadowRadius = 5
    // 确保视图不被当前 UIView 视图遮挡
    popView.layer.zPosition = CGFloat(INT8_MAX)
}

主视图 3D 设计(核心内容)

如何实现 当前 UIViewController 缩放效果

// 动画效果
fileprivate func transformAnimation(type: LWAnimateType) -> CATransform3D {
    var transform = CATransform3DIdentity
    switch type {
    case .first:
        // 视图角度
        transform.m34 = -1.0 / 2000;
        // 尺寸缩小(transform对象,X轴,Y轴,Z轴)
        transform = CATransform3DScale(transform, 1, 1, 1)
        // 沿某轴旋转(transform对象,旋转角度,X轴,Y轴,Z轴)
        let angel = 15.0 * (CGFloat)(M_PI) / 180.0
        transform = CATransform3DRotate(transform, angel, 1, 0, 0)
    case .second:
        // 第二次 变形实在第一次的基础上
        transform.m34 = transformAnimation(type: .first).m34
        // 沿着某轴移动(transform对象,X轴,Y轴,Z轴)
        transform = CATransform3DTranslate(transform, 0, 10, 0)
        // 尺寸缩小(transform对象,X轴,Y轴,Z轴)
        transform = CATransform3DScale(transform, (SCREEN_WIDTH-40)/SCREEN_WIDTH, (SCREEN_HEIGHT-20)/SCREEN_HEIGHT, 1)
    }
    return transform
}

分析:3D 缩放分两步:

  1. 整个视图先沿着 X 轴旋转 15%;
  2. 整个视图再 X轴 左右个缩小 20尺寸,Y轴缩小 10尺寸;
3D步骤一
3D步骤一

3D步骤二
3D步骤二

popView 设置 frame

除了 当前 UIViewController 缩放动画,popView 也是需要弹出动画

// 获取 popView 和 maskView 新的 frame
func getViewFrame(type: LWActionType) -> CGRect {
    var frame:CGRect
    switch type {
    case .popUp:
        // popView 出现时的 frame
        frame = popView.frame
        frame.origin.y = SCREEN_HEIGHT - popViewHeight
    case .popDown:
        // popView 隐藏时的 frame
        frame = popView.frame
        frame.origin.y = SCREEN_HEIGHT
    }
    return frame
}

设计 弹出动画与 弹回动画

通过 iOS 自动动画将 当前 UIViewController 缩放、maskView 遮挡以及popView 弹出结合起来

// 弹出视图操作
func popUp() {
    // 分别将 maskView 和 popView 添加到 应用主视图中,脱离与当前的 UIViewController 便于分离缩放动画
    UIApplication.shared.keyWindow?.addSubview(maskView)
    UIApplication.shared.keyWindow?.addSubview(popView)
    UIApplication.shared.keyWindow?.bringSubview(toFront: maskView)
    UIApplication.shared.keyWindow?.bringSubview(toFront: popView)
    // 获取最终的 popView 的弹出层位置尺寸,使用动画实现弹出效果
    let popViewFrame = getViewFrame(type: .popUp)
    UIView.animate(withDuration: self.duration,
                   delay: 0,
                   options: UIViewAnimationOptions.curveEaseInOut,
                   animations: {
                    // 当前 UIViewController 缩放动画一
                    self.rootView.layer.transform = self.transformAnimation(type: .first)
    }) { (bool) in
        UIView.animate(withDuration: self.duration, delay: 0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
            // 当前 UIViewController 缩放动画二
            self.rootView.layer.transform = self.transformAnimation(type: .second)
            self.popView.frame = popViewFrame
        }, completion: nil)
    }
}

// 弹回操作
func popDown() {
    self.maskView.removeFromSuperview()
    let popViewFrame = getViewFrame(type: .popDown)
    UIView.animate(withDuration: self.duration,
                   delay: 0,
                   options: UIViewAnimationOptions.curveEaseInOut,
                   animations: {
                    self.rootView.layer.transform = self.transformAnimation(type: .first)
    }) { (bool) in
        UIView.animate(withDuration: self.duration, delay: 0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
            self.popView.frame = popViewFrame
            self.rootView.layer.transform = CATransform3DIdentity
        }, completion: {(bool) in
            self.popView.removeFromSuperview()
        })
    }
}

使用说明

其实我写这个类,用起来还是比较方便的,只需要将需要弹出的UIVeiwController 继承 LWPopViewController 即可,popView 将是父类属性,只需要在其中 addSubView 即可使用,通过 self.popUp() 和 self.popDown() 调用弹出、弹回事件。

结合上一节的内容,简单写了一下调用代码

class GoodsDetailController: LWPopController {
    
    
    var imageScrollView = ImageScrollView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: 150))
    var data = [ImageScrollData]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.white
        edgesForExtendedLayout = .init(rawValue: 0)
        self.title = "图片无限滚动"
        self.view.addSubview(imageScrollView)
        for i in 1 ... 6 {
            let item = ImageScrollData(imageUrl: "image_scroll_0\(i).jpg", imageDescribe: nil)
            data.append(item)
        }
        imageScrollView.data = data
        self.popView.backgroundColor = .red
        initView()
    }

    
    func initView() {
        self.edgesForExtendedLayout = .init(rawValue: 0)
        self.view.addSubview(btnPopup)
        btnPopup.frame = CGRect(x: 10, y: imageScrollView.bottomY + 10, width: SCREEN_WIDTH - 20, height: 40)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func showPop() {
        self.popUp()
    }
    
    func closePage() {
        self.dismiss(animated: true, completion: nil)
    }
    
    fileprivate var btnPopup: UIButton = {
        let object = UIButton()
        object.tag = 1
        object.layer.cornerRadius = 2
        object.backgroundColor = UIColor.green
        object.setTitle("图片无限滚动", for: .normal)
        object.setTitleColor(UIColor.black, for: .normal)
        object.addTarget(self, action: #selector(showPop), for:
            .touchUpInside)
        return object
    }()

}

最终效果

3D步骤二
3D步骤二
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 我们的相遇是意外,是运气,也是命运。这十八年以来 ,我用了所有的运气,只为老天安排遇见你。 你知道吗?在没有遇见你...
    缥缈仙子阅读 175评论 2 6
  • 亲子阅读一直坚持,没有估算过陈小冠认多少字,只是这半年加入了指读,假期决定认字,选择了 这套书新版是一半教程使用,...
    木木sani阅读 230评论 0 0
  • 谷嘉诚用电热壶烧了三次,才勉强兑了浅浅一浴缸底的温水。伍嘉成三下两下就把全身的衣服脱去,光着身子晃悠过来,长腿一迈...
    看来这空调是修不好了阅读 1,052评论 0 1
  • 妹妹你大胆地往前走啊, 往前走, 莫回头! 图中的神奇女纸还是丢丢。 不知道酷酷有意思的她去哪里浪了,浪迹哪里去了...
    晶童阅读 233评论 6 4