你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> iOS 開源一個簡單的訂餐App UI框架

iOS 開源一個簡單的訂餐App UI框架

編輯:IOS開發基礎

前言

學 Swift 也有一段時間了,做了一些小的 demo,有興趣的可以看我的100 Days of Swift。一直想做個完整的項目,發現這邊學校的外賣訂餐也逐漸流行起來,不像中國有那麼多強大的外賣軟件,美國也有,但不多,起碼中國人對那些軟件都不太熟知也不怎麼用。打算專門針對午餐的外賣做個app,做了幾天,只做出個 UI,看上去很小的軟件,新手做起來感覺東西還是有點多。Swift 如何與後端交互之類的之後再慢慢學吧,有大神願意在評論區給幾個教程就更好了。數據庫之類的我都挺熟悉,SQL 或者 MongoDB。

BTW, 想了解 MongoDB 的可以看我的這兩篇文章-Part 1,Part 2,我之前做了個完整的網站 demo,前後端都實現了,建於 Heroku,感覺挺酷的。

目錄

在這個 app 中,所有 UI 都是用代碼創建的,你可以在100 Days of Swift 看到,我之前練習的時候都是用的 storyboard,但是到了10頁以上感覺 storyboard 就開始有點亂了,特別是那些 segue 的線牽得滿屏幕都是的時候。之後我就開始用 SnapKit 做 UI 了,雖然比起 CSS 來,還是有點不方便,但用起來感覺還行。下面我大概羅列了一些實現的基本功能:

  • 引導頁

  • 午餐菜單(tableView)

  • 購物車,動畫

  • 下拉刷新

  • 自定義個人主頁 (collectionView)

  • Reminder 和 Setting 需要後台,就用了 Alert 來簡單響應了

  • 全屏右滑退出

具體代碼請看我的 Github, 下面我就主要展示一下效果,稍微講一下實現過程,代碼中已有很多注釋。

引導頁

1960892-c779c8126ee12043.gif

引導頁我是用 collectionView 做的,剛開始先判斷要不要進入引導頁,如果版本更新,則進入。collectionView 滑動方向設置為 .horizontal,設置任意數量的頁數。添加一個啟動的 startButton,設置前幾頁都為 startButton.isHidden = true,最後一頁的時候顯示出來,再添加一個漸出的顯示動畫。

菜單和購物車

1960892-f40077e6c65448f8.gif

shoppingCart

菜單可以下拉刷新,本打算自定義下拉刷新,就像 ALin 的項目中那樣,但是好像有點問題,我就用了自帶的 UIRefreshControl,下拉的時候顯示刷新的時間,稍微調整了下時間的 format。代碼很簡單

let dateString = DateFormatter.localizedString(from: NSDate() as Date, dateStyle: .medium, timeStyle: .short)
self.refreshControl.attributedTitle = NSAttributedString(string: "Last updated on \(dateString)", attributes: attributes)
self.refreshControl.tintColor = UIColor.white

然後做了個購物車的動畫,將菜單裡的圖片先放大後縮小“拋入”購物車,其實是沿著 UIBezierPath 走的一個路徑,這段動畫完了之後,在 animationDidStop() 裡做購物車圖片的抖動,和顯示購買的物品數量,那個 countLabel 是個長寬都為 15 的在購物車圖片右上角的 UILabel()

先實現一個回調方法,當點擊了cell上的購買按鈕後觸發

func menuListCell(_ cell: MenuListCell, foodImageView: UIImageView)
    {
        guard let indexPath = tableView.indexPath(for: cell) else { return }

        // retrieve the current food model, add it to shopping cart model
        let model = foodArray[indexPath.section][indexPath.row]
        addFoodArray.append(model)
        // recalculate the frame of imageView, start animation
        var rect = tableView.rectForRow(at: indexPath)
        rect.origin.y -= tableView.contentOffset.y
        var headRect = foodImageView.frame
        headRect.origin.y = rect.origin.y + headRect.origin.y - 64
        startAnimation(headRect, foodImageView: foodImageView)
    }

這是點擊購買之後的動畫實現:

fileprivate func startAnimation(_ rect: CGRect, foodImageView: UIImageView)
    {
        if layer == nil {
            layer = CALayer()
            layer?.contents = foodImageView.layer.contents
            layer?.contentsGravity = kCAGravityResizeAspectFill
            layer?.bounds = rect
            layer?.cornerRadius = layer!.bounds.height * 0.5
            layer?.masksToBounds = true
            layer?.position = CGPoint(x: foodImageView.center.x, y: rect.minY + 96)
            KeyWindow.layer.addSublayer(layer!)

            // animation path
            path = UIBezierPath()
            path!.move(to: layer!.position)
            path!.addQuadCurve(to: CGPoint(x:SCREEN_WIDTH - 25, y: 35), controlPoint: CGPoint(x: SCREEN_WIDTH * 0.5, y: rect.origin.y - 80))
        }
        groupAnimation()
    }

這是放大,縮小,拋入購物車的組動畫

    // start group animation: throw, larger, smaller image
    fileprivate func groupAnimation()
    {
        tableView.isUserInteractionEnabled = false

        // move path
        let animation = CAKeyframeAnimation(keyPath: "position")
        animation.path = path!.cgPath
        animation.rotationMode = kCAAnimationRotateAuto

        // larger image
        let bigAnimation = CABasicAnimation(keyPath: "transform.scale")
        bigAnimation.duration = 0.5
        bigAnimation.fromValue = 1
        bigAnimation.toValue = 2
        bigAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)        // smaller image
        let smallAnimation = CABasicAnimation(keyPath: "transform.scale")
        smallAnimation.beginTime = 0.5
        smallAnimation.duration = 1
        smallAnimation.fromValue = 2
        smallAnimation.toValue = 0.5
        smallAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)        // group animation
        let groupAnimation = CAAnimationGroup()
        groupAnimation.animations = [animation, bigAnimation, smallAnimation]
        groupAnimation.duration = 1.5
        groupAnimation.isRemovedOnCompletion = false
        groupAnimation.fillMode = kCAFillModeForwards
        groupAnimation.delegate = self
        layer?.add(groupAnimation, forKey: "groupAnimation")
    }

組動畫結束後的一些動畫效果。

    // end image animation, start other animations
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool)
    {        if anim == layer?.animation(forKey: "groupAnimation")
        {            // start user interaction
            tableView.isUserInteractionEnabled = true

            // hide layer
            layer?.removeAllAnimations()
            layer?.removeFromSuperlayer()
            layer = nil

            // if user buy any food, show the count label
            if self.addFoodArray.count > 0 {
                addCountLabel.isHidden = false
            }            // show the count label
            let goodCountAnimation = CATransition()
            goodCountAnimation.duration = 0.25
            addCountLabel.text = "\(self.addFoodArray.count)"
            addCountLabel.layer.add(goodCountAnimation, forKey: nil)            // shopping cart shaking
            let cartAnimation = CABasicAnimation(keyPath: "transform.translation.y")
            cartAnimation.duration = 0.25
            cartAnimation.fromValue = -5
            cartAnimation.toValue = 5
            cartAnimation.autoreverses = true
            cartButton.layer.add(cartAnimation, forKey: nil)
        }
    }

購物車裡面可以增加/減少購買數量,總價跟著會動態變動。主要是有用到了兩個東西,一個是 selected 變量,一個是 reCalculateCount() 函數。根據 selected 來決定最後的總價,如果有變動,則重新計算 (reCalculateCount)。

fileprivate func reCalculateCount()
    {
        for model in addFoodArray! {
            if model.selected == true {
                price += Float(model.count) * (model.vipPrice! as NSString).floatValue
            }
        }
        // assign price
        let attributeText = NSMutableAttributedString(string: "Subtotal: \(self.price)")
        attributeText.setAttributes([NSForegroundColorAttributeName: UIColor.red], range: NSMakeRange(5, attributeText.length - 5))
        totalPriceLabel.attributedText = attributeText
        price = 0
        tableView.reloadData()
    }

沒有實現 Pay() 功能。打算之後嘗試 Apple Pay,之前用慣了支付寶,剛來美國的時候很難受,其實很多地方中國都已經比美國好很多了。還好現在有了 Apple Pay,還挺好用的。

自定義個人主頁

1960892-662897f0535aff4a.gif

profile

本來打算做成簡書那樣,但是。。作為新手感覺還是有點難度。也是因為我這 app 裡沒有必要實現那些,就沒仔細研究。

如前面提到的這頁用的 collectionView,兩個 section,一個是 UserCollectionViewCell, 下面是 HistoryCollectionViewCell。 下面這個 section 像一個 table 的 section,有一個會自動懸浮的 header,這 header 用的是 ALin 大神的輪子,LevitateHeaderFlowLayout(),當然這個文件的 copyright 是用他的名字的。

class CollectionViewFlowLayout: LevitateHeaderFlowLayout {
    override func prepare() {
        super.prepare()

        collectionView?.alwaysBounceVertical = true
        scrollDirection = .vertical
        minimumLineSpacing = 5
        minimumInteritemSpacing = 0
    }
}

這項目總體來說應該算很小的,如果後端也實現了,也算一個蠻完整的小項目了吧。

最後

特別感謝 ALin 大神的開源項目花田小憩。他造了很多小輪子,我有些是參考他的。站在巨人的肩膀上就是爽哈哈哈。

下面有幾篇文章分享給大家,其實很多時候我們都可以從一個人說的話,寫的字裡看出一個人的水平。期待哪天自己也能寫出高水平的文章。加油!

  • 講 tableview 的代碼布局:Refactoring table view data source and delegate methods

  • 對我幫助超級大的 ALin 大神的開源項目:iOS高仿:花田小憩3.0.1

  • 博主對架構與框架的看法挺有意思:架構(Architecture)和框架(Framework)雜談

  • ALin 大神的懸浮 header 也是從這裡來的。



文章轉自 諸葛俊偉的簡書
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved