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閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容

  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,151評論 4 61
  • 我們的相遇是意外,是運氣,也是命運。這十八年以來 ,我用了所有的運氣,只為老天安排遇見你。 你知道嗎?在沒有遇見你...
    縹緲仙子閱讀 181評論 2 6
  • 親子閱讀一直堅持,沒有估算過陳小冠認多少字,只是這半年加入了指讀,假期決定認字,選擇了 這套書新版是一半教程使用,...
    木木sani閱讀 235評論 0 0
  • 谷嘉誠用電熱壺燒了三次,才勉強兌了淺淺一浴缸底的溫水。伍嘉成三下兩下就把全身的衣服脫去,光著身子晃悠過來,長腿一邁...
    看來這空調是修不好了閱讀 1,069評論 0 1
  • 妹妹你大膽地往前走啊, 往前走, 莫回頭! 圖中的神奇女紙還是丟丟。 不知道酷酷有意思的她去哪里浪了,浪跡哪里去了...
    晶童閱讀 242評論 6 4