iOS 开源一个简单的订餐App UI框架

136350929 8年前
   <h3><strong>前言</strong></h3>    <p>学 Swift 也有一段时间了,做了一些小的 demo。一直想做个完整的项目,发现这边学校的外卖订餐也逐渐流行起来,不像中国有那么多强大的外卖软件,美国也有,但不多,起码中国人对那些软件都不太熟知也不怎么用。打算专门针对午餐的外卖做个app,做了几天,只做出个 UI,看上去很小的软件,新手做起来感觉东西还是有点多。 <strong>Swift 如何与后端交互</strong> 之类的之后再慢慢学吧,有数据库之类的我都挺熟悉,SQL 或者 MongoDB。</p>    <h3><strong>目录</strong></h3>    <p>在这个 app 中,所有 UI 都是用代码创建的,你可以在 100 Days of Swift 看到,我之前练习的时候都是用的 storyboard,但是到了10页以上感觉 storyboard 就开始有点乱了,特别是那些 segue 的线牵得满屏幕都是的时候。之后我就开始用 <strong>SnapKit</strong> 做 UI 了,虽然比起 CSS 来,还是有点不方便,但用起来感觉还行。下面我大概罗列了一些实现的基本功能:</p>    <ul>     <li> <p>引导页</p> </li>     <li> <p>午餐菜单(tableView)</p> </li>     <li> <p>购物车,动画</p> </li>     <li> <p>下拉刷新</p> </li>     <li> <p>自定义个人主页 (collectionView)</p> </li>     <li> <p>Reminder 和 Setting 需要后台,就用了 Alert 来简单响应了</p> </li>     <li> <p>全屏右滑退出</p> </li>    </ul>    <h3><strong>引导页</strong></h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2a603b980672de6c80e336f1da0d7b18.gif"></p>    <p>引导页我是用 collectionView 做的,刚开始先判断要不要进入引导页,如果版本更新,则进入。collectionView 滑动方向设置为 .horizontal ,设置任意数量的页数。添加一个启动的 startButton ,设置前几页都为 startButton.isHidden = true ,最后一页的时候显示出来,再添加一个渐出的显示动画。</p>    <h3><strong>菜单和购物车</strong></h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ec80d949c4beb087bcad51663f800b7b.gif"></p>    <p>shoppingCart</p>    <p>菜单可以下拉刷新,本打算自定义下拉刷新,就像 ALin 的项目中那样,但是好像有点问题,我就用了自带的 UIRefreshControl ,下拉的时候显示刷新的时间,稍微调整了下时间的 format。代码很简单</p>    <pre>  <code class="language-objectivec">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</code></pre>    <p>然后做了个购物车的动画,将菜单里的图片先放大后缩小“抛入”购物车,其实是沿着 UIBezierPath 走的一个路径,这段动画完了之后,在 animationDidStop() 里做购物车图片的抖动,和显示购买的物品数量,那个 countLabel 是个长宽都为 15 的在购物车图片右上角的 UILabel() 。</p>    <p>先实现一个回调方法,当点击了cell上的购买按钮后触发</p>    <pre>  <code class="language-objectivec">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)      }</code></pre>    <p>这是点击购买之后的动画实现:</p>    <pre>  <code class="language-objectivec">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()      }</code></pre>    <p>这是放大,缩小,抛入购物车的组动画</p>    <pre>  <code class="language-objectivec">    // 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")      }</code></pre>    <p>组动画结束后的一些动画效果。</p>    <pre>  <code class="language-objectivec">    // 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)          }      }</code></pre>    <p>购物车里面可以增加/减少购买数量,总价跟着会动态变动。主要是有用到了两个东西,一个是 selected 变量,一个是 reCalculateCount() 函数。根据 selected 来决定最后的总价,如果有变动,则重新计算 (reCalculateCount)。</p>    <pre>  <code class="language-objectivec">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()      }</code></pre>    <p>没有实现 Pay() 功能。打算之后尝试 <strong>Apple Pay</strong> ,之前用惯了支付宝,刚来美国的时候很难受,其实很多地方中国都已经比美国好很多了。还好现在有了 <strong>Apple Pay</strong> ,还挺好用的。</p>    <h3><strong>自定义个人主页</strong></h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ce4bf6aad97834a127a46c8268bedcdf.gif"></p>    <p>profile</p>    <p>本来打算做成简书那样,但是。。作为新手感觉还是有点难度。也是因为我这 app 里没有必要实现那些,就没仔细研究。</p>    <p>如前面提到的这页用的 collectionView,两个 section,一个是 UserCollectionViewCell , 下面是 HistoryCollectionViewCell 。 下面这个 section 像一个 table 的 section,有一个会自动悬浮的 header,这 header 用的是 ALin 大神的轮子, LevitateHeaderFlowLayout() ,当然这个文件的 copyright 是用他的名字的。</p>    <pre>  <code class="language-objectivec">class CollectionViewFlowLayout: LevitateHeaderFlowLayout {      override func prepare() {          super.prepare()            collectionView?.alwaysBounceVertical = true          scrollDirection = .vertical          minimumLineSpacing = 5          minimumInteritemSpacing = 0      }  }</code></pre>    <p>这项目总体来说应该算很小的,如果后端也实现了,也算一个蛮完整的小项目了吧。</p>    <h3> </h3>    <p> </p>    <p>来自:http://www.cocoachina.com/swift/20161014/17744.html</p>    <p> </p>