//联系人:石虎  QQ: 1224614774昵称:嗡嘛呢叭咪哄

iOS高仿爱鲜蜂

前言

2016年匆匆的就过去了,又老了一岁,这一年起起伏伏,有笑声也有眼泪,感谢陪伴在我身边的人.

关于项目(代码下载地址在文章最下面点击GitHub链接)

本次开源项目为爱鲜蜂,一款电商APP,使用语言Swift2.0,开发工具Xcode7.0.1.

项目为纯代码开发,没有使用XIB和StoryBoard.开发周期大概为2个月左右(工作闲暇之余).

数据都是本地数据,辅助开发软件:PhotoShop CS6(图片处理),Charles(抓包工具).

写的比较匆忙,很多地方无法尽善尽美,如果有建议和可优化的地方可在文章底部留言,我会一一查看的并回复的.

项目效果图

效果图1

效果图2

效果图3

效果图4

效果图5

效果图6

效果图7

效果图8

效果图9

项目详细讲解(根据启动流程)

引导页和AD(广告)页

当程序被打开时,在创建KeyWindow的RootViewController时判断是否是首次登陆,这里的逻辑是如果用户是首次打开应用的话显示引导页,当点击引导页最后一页的立即体验直接进入TabBarController,不显示广告页(效果如下图)

引导页

如果用户不是首次打开应用的话,则显示广告页,并且在4秒后以放大并且透明的效果进入TabBarController(效果如下图)

广告页

逻辑代码如下

    // MARK: - Public Methodprivate func buildKeyWindow() {window = UIWindow(frame: ScreenBounds)window!.makeKeyAndVisible()let isFristOpen = NSUserDefaults.standardUserDefaults().objectForKey("isFristOpenApp")if isFristOpen == nil {window?.rootViewController = GuideViewController()NSUserDefaults.standardUserDefaults().setObject("isFristOpenApp", forKey: "isFristOpenApp")} else {loadADRootViewController()}}

引导页考虑到循环利用,使用的是UICollectionView实现

AD(广告)页需要注意的是ADViewController有时会存在加载广告图片失败的情况,如果加载失败,发送加载图片失败的通知,window直接将tarBarController作为keyWindow即可

关于引导页和AD页的实现代码,我就不详细讲述了,源代码都有,有兴趣的读者可以打开代码自行研究

AnimationTabBarController(带有动画的TabBarItem)

这里有个小故事,我是无意中发现爱鲜蜂底部TabBarItem有点击的动画,感觉挺有意思的,尝试着自己实现了同样的功能,然后才突发奇将后续的功能都给实现了.先看下底部UITabBarItem的动画(效果如下图)

TaBarItem动画效果

底部的TabBarItem动画使用了三方框架RAMAnimatedTabBar,由于原来的框架 只能通过StoryBoard初始化控件,并且无法满足项目需求,所以对框架进行了大量修改,其原理很简单,就是在TabBarController初始化时,通过拦截Items,重新创建一套相同的View,并且在每个View上添加ImageView和Label,在View的点击事件中,控制动画即可.用这种方式也可以轻松完成一些看似复杂的动画,如下图所示,其实通过ImageView的序列动画就可以轻松完成,只是需要在AE中做出动画序列图即可.

通过序列动画达到的效果

首页

首页由三部分构成,顶部的轮播图(PageScrollView),轮播图下面的活动按钮,以及UICollectionView.(如下图所示)

首页效果图

其中将PageScrollView与活动按钮封装成了UICollectionView的headView,通过代理方法将点击的事件,需要注意的是活动按钮并不是固定只有四个,这里是根据服务器返回的数据创建的,有时候会有多个,需要根据数量控制列数,通过代理方法将高度传给首页的控制器.

PageScrollView(轮播图)

这里采用循环利用机制写的,只创建3个ImageView即可,在同一时刻屏幕中最多只会显示2个ImageView,当需要展示新的ImageView,只需要将缓存数组中的没有展示的ImageView拿出来展示即可,这里为了方便大家将项目移植到自己的项目中使用,我将代码全部拷贝过来了,需要替换数据修改headData内的数据

import UIKitclass PageScrollView: UIView {private let imageViewMaxCount = 3private var imageScrollView: UIScrollView!private var pageControl: UIPageControl!private var timer: NSTimer?private var placeholderImage: UIImage?private var imageClick:((index: Int) -> ())?var headData: HeadResources? {didSet {if timer != nil {timer!.invalidate()timer = nil}if headData?.data?.focus?.count >= 0 {pageControl.numberOfPages = (headData?.data?.focus?.count)!pageControl.currentPage = 0updatePageScrollView()startTimer()}}}override init(frame: CGRect) {super.init(frame: frame)buildImageScrollView()buildPageControl()}convenience init(frame: CGRect, placeholder: UIImage, focusImageViewClick:((index: Int) -> Void)) {self.init(frame: frame)placeholderImage = placeholderimageClick = focusImageViewClick}required init?(coder aDecoder: NSCoder) {fatalError("init(coder:) has not been implemented")}override func layoutSubviews() {super.layoutSubviews()imageScrollView.frame = boundsimageScrollView.contentSize = CGSizeMake(CGFloat(imageViewMaxCount) * width, 0)for i in 0...imageViewMaxCount - 1 {let imageView = imageScrollView.subviews[i] as! UIImageViewimageView.userInteractionEnabled = trueimageView.frame = CGRectMake(CGFloat(i) * imageScrollView.width, 0, imageScrollView.width, imageScrollView.height)}let pageW: CGFloat = 80let pageH: CGFloat = 20let pageX: CGFloat = imageScrollView.width - pageWlet pageY: CGFloat = imageScrollView.height - pageHpageControl.frame = CGRectMake(pageX, pageY, pageW, pageH)updatePageScrollView()}// MARK: BuildUIprivate func buildImageScrollView() {imageScrollView = UIScrollView()imageScrollView.bounces = falseimageScrollView.showsHorizontalScrollIndicator = falseimageScrollView.showsVerticalScrollIndicator = falseimageScrollView.pagingEnabled = trueimageScrollView.delegate = selfaddSubview(imageScrollView)for _ in 0..<3 {let imageView = UIImageView()let tap = UITapGestureRecognizer(target: self, action: "imageViewClick:")imageView.addGestureRecognizer(tap)imageScrollView.addSubview(imageView)}}private func buildPageControl() {pageControl = UIPageControl()pageControl.hidesForSinglePage = truepageControl.pageIndicatorTintColor = UIColor(patternImage: UIImage(named: "v2_home_cycle_dot_normal")!)pageControl.currentPageIndicatorTintColor = UIColor(patternImage: UIImage(named: "v2_home_cycle_dot_selected")!)addSubview(pageControl)}//MARK: 更新内容private func updatePageScrollView() {for var i = 0; i < imageScrollView.subviews.count; i++ {    let imageView = imageScrollView.subviews[i] as! UIImageViewvar index = pageControl.currentPageif i == 0 {index--} else if 2 == i {index++}if index < 0 {index = self.pageControl.numberOfPages - 1} else if index >= pageControl.numberOfPages {index = 0}imageView.tag = indexif headData?.data?.focus?.count > 0 {imageView.sd_setImageWithURL(NSURL(string: headData!.data!.focus![index].img!), placeholderImage: placeholderImage)}}imageScrollView.contentOffset = CGPointMake(imageScrollView.width, 0)}// MARK: Timerprivate func startTimer() {timer = NSTimer(timeInterval: 3.0, target: self, selector: "next", userInfo: nil, repeats: true)NSRunLoop.mainRunLoop().addTimer(timer!, forMode: NSRunLoopCommonModes)}private func stopTimer() {timer?.invalidate()timer = nil}func next() {imageScrollView.setContentOffset(CGPointMake(2.0 * imageScrollView.frame.size.width, 0), animated: true)}// MARK: ACTIONfunc imageViewClick(tap: UITapGestureRecognizer) {if imageClick != nil {imageClick!(index: tap.view!.tag)}}
}// MARK:- UIScrollViewDelegate
extension PageScrollView: UIScrollViewDelegate {func scrollViewDidScroll(scrollView: UIScrollView) {var page: Int = 0var minDistance: CGFloat = CGFloat(MAXFLOAT)for i in 0..<imageScrollView.subviews.count {let imageView = imageScrollView.subviews[i] as! UIImageViewlet distance:CGFloat = abs(imageView.x - scrollView.contentOffset.x)if distance < minDistance {minDistance = distancepage = imageView.tag}}pageControl.currentPage = page}func scrollViewWillBeginDragging(scrollView: UIScrollView) {stopTimer()}func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {startTimer()}func scrollViewDidEndDecelerating(scrollView: UIScrollView) {updatePageScrollView()}func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {updatePageScrollView()}
}

首页UICollectionView

首页的CollectionView采用了两种Cell,一种是只有ImageView的Cell,一种是商品的Cell(如图所示)

首页Cell样式一

首页Cell样式二

通过判断indexPath.section展示对应Cell即可.

新Cell出现的停靠动画,如图

停靠动画

通过实现UICollectionViewDelegate,在willDisplayCell代理方法完成动画,代码如下

    func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {if indexPath.section == 0 && (indexPath.row == 0 || indexPath.row == 1) {return}if isAnimation {startAnimation(cell, offsetY: 80, duration: 1.0)}}private func startAnimation(view: UIView, offsetY: CGFloat, duration: NSTimeInterval) {view.transform = CGAffineTransformMakeTranslation(0, offsetY)UIView.animateWithDuration(duration, animations: { () -> Void inview.transform = CGAffineTransformIdentity})}

添加商品动画

当用户点击加号时,会出现如下如所示动画

添加商品到购物车动画效果

添加商品到购物车基于CoreAnimation(核心动画)实现,通过对ImageView的layer添加缩放,透明度以及路径动画实现.代码如下

import UIKitclass AnimationViewController: BaseViewController {var animationLayers: [CALayer]?var animationBigLayers: [CALayer]?// MARK: 商品添加到购物车动画func addProductsAnimation(imageView: UIImageView) {if (self.animationLayers == nil){self.animationLayers = [CALayer]();}let frame = imageView.convertRect(imageView.bounds, toView: view)let transitionLayer = CALayer()transitionLayer.frame = frametransitionLayer.contents = imageView.layer.contentsself.view.layer.addSublayer(transitionLayer)self.animationLayers?.append(transitionLayer)let p1 = transitionLayer.position;let p3 = CGPointMake(view.width - view.width / 4 - view.width / 8 - 6, self.view.layer.bounds.size.height - 40);let positionAnimation = CAKeyframeAnimation(keyPath: "position")let path = CGPathCreateMutable();CGPathMoveToPoint(path, nil, p1.x, p1.y);CGPathAddCurveToPoint(path, nil, p1.x, p1.y - 30, p3.x, p1.y - 30, p3.x, p3.y);positionAnimation.path = path;let opacityAnimation = CABasicAnimation(keyPath: "opacity")opacityAnimation.fromValue = 1opacityAnimation.toValue = 0.9opacityAnimation.fillMode = kCAFillModeForwardsopacityAnimation.removedOnCompletion = truelet transformAnimation = CABasicAnimation(keyPath: "transform")transformAnimation.fromValue = NSValue(CATransform3D: CATransform3DIdentity)transformAnimation.toValue = NSValue(CATransform3D: CATransform3DScale(CATransform3DIdentity, 0.2, 0.2, 1))let groupAnimation = CAAnimationGroup()groupAnimation.animations = [positionAnimation, transformAnimation, opacityAnimation];groupAnimation.duration = 0.8groupAnimation.delegate = self;transitionLayer.addAnimation(groupAnimation, forKey: "cartParabola")}override func animationDidStop(anim: CAAnimation, finished flag: Bool) {if self.animationLayers?.count > 0 {let transitionLayer = animationLayers![0]transitionLayer.hidden = truetransitionLayer.removeFromSuperlayer()animationLayers?.removeFirst()view.layer.removeAnimationForKey("cartParabola")}}
}

闪电超市

闪电超市很明显由有2个TableView构成,如图所示

闪电超市效果图

这里采用了2个控制器分别管理各自的TableView,将TableView2添加到VC2上,将VC2.view添加到VC1.view上,然后再通过VC1.addChildViewController将VC2添加到VC1的子控制器中,这样既降低了代码的复杂性,有提升了代码维护性,各自管理各自的TableView.

很多联动的操作都是通过UITableViewDelegate中实现的,有兴趣的同学可参照代码自行研究.

购物车

购物车采用了modal的形式出现.样式上有两种情况,当购物车里没有商品时,购物车显示为空(如下图)

当购物车为空

当购物车中有商品时候,显示商品信息(如下图)

有商品时的购物车

这里封装了一个UserShopCar单利类,专门用来管理用户购物车,保存用户添加到购物车的商品种类,商品总数,商品价格等,在购物车VC将要出现时候,判断购物车是否为空,如果为空则显示去逛逛,如果不为空则显示商品的信息.内部细节请参考代码

购物车上红色圆圈

购物车红色圆圈也是通过单利类来实现的,内部提供俩个方法,添加商品和移除商品,方法内部包含动画效果,当添加商品或者减掉商品时,调用对象对应的方法即可.

我的

先看下效果

我的效果图

我的页面是项目中最为复杂的页面,包含了许多效果.我大体讲一下思路,我的页面是由顶部的View以及一个TableView构成,TableView有一个headView,分别是我的订单,优惠劵以及我的消息,通过闭包的回调完成点击的事件.这个页面比较简单,不过多叙述了.

右上角设置按钮

设置效果图

设置页面没有使用TableView,单纯的用View搭建的.

清理缓存这稍微说一下吧,同样也封装了一个工具类,提供四个方法,分别是参看单个文件的大小,查看全部文件大小文件大小,同步将文件夹清除以及异步清除文件夹.有需要的同学可以直接copy走,那去使用,path是文件的路径

import UIKitclass FileTool: NSObject {static let fileManager = NSFileManager.defaultManager()/// 计算单个文件的大小class func fileSize(path: String) -> Double {if fileManager.fileExistsAtPath(path) {var dict = try? fileManager.attributesOfItemAtPath(path)if let fileSize = dict![NSFileSize] as? Int{return Double(fileSize) / 1024.0 / 1024.0}}return 0.0}/// 计算整个文件夹的大小class func folderSize(path: String) -> Double {var folderSize: Double = 0if fileManager.fileExistsAtPath(path) {let chilerFiles = fileManager.subpathsAtPath(path)for fileName in chilerFiles! {let tmpPath = path as NSStringlet fileFullPathName = tmpPath.stringByAppendingPathComponent(fileName)folderSize += FileTool.fileSize(fileFullPathName)}return folderSize}return 0}/// 清除文件 同步class func cleanFolder(path: String, complete:(str: String) -> ()) {var str: String?let chilerFiles = self.fileManager.subpathsAtPath(path)for fileName in chilerFiles! {let tmpPath = path as NSStringlet fileFullPathName = tmpPath.stringByAppendingPathComponent(fileName)if self.fileManager.fileExistsAtPath(fileFullPathName) {do {try self.fileManager.removeItemAtPath(fileFullPathName)str = "清理成功"} catch _ {str = "清理失败"}}}complete(str: str!)}/// 清除文件 异步class func cleanFolderAsync(path: String, complete:(str: String) -> ()) {var str: String?let queue = dispatch_queue_create("cleanQueue", nil)dispatch_async(queue) { () -> Void inlet chilerFiles = self.fileManager.subpathsAtPath(path)for fileName in chilerFiles! {let tmpPath = path as NSStringlet fileFullPathName = tmpPath.stringByAppendingPathComponent(fileName)if self.fileManager.fileExistsAtPath(fileFullPathName) {do {try self.fileManager.removeItemAtPath(fileFullPathName)str = "清理成功"} catch _ {str = "清理失败"}}}complete(str: str!)}}
}

我的订单

我的订单由2个控制器构成,分别是MyOrderViewController(我的订单)和OrderViewDetailViewController(订单详情)

我的订单效果

我的订单

一个TableView搞定,cell的样式也并不复杂,这里有个小细节就是需要判断订单商品种类的个数,如果是4个以上,只显示五张图片,并且第五张图片以...图片显示,在设置cell的model时,判断商品种类个数即可实现.发福利等按钮是根据服务器返回的数据创建的,不同类型的Button会有不同的Type,type的值为int类型的,这里可以将button的tag设置对应的typr,这点击的时候判断button的tag,通过Swith语法执行对应的操作就可以了.

订单详情页

订单详情页有两部分,分别是订单状态以及订单详情,通过导航栏的titleView(也就是UISegmentedControl)来切换显示不同界面.

订单详情页也是一个TableView就可以搞定,服务器返回是一个数组,这里的逻辑是,当前状态是0时,圆形图片为黄色,并且没有上面的线,最下面的状态没有下边的线,这里的做法是给cell的model赋值的时候,将indexPath一同传入给Cell,判断indexPath.row是多少,如果是0,就将圆形图片显示为黄色,并且隐藏上半部分线,同理当indexPath.row等于状态数组的count-1时,隐藏下半部分的线即可搞定.

订单详情也是通过TableView实现的,采用tableView是考虑到商品种类的cell可以循环利用,顶部的订单细信息和收货人地址为TableHeadView(效果如下图)

订单详情结构

底部评价为tableFootView(效果如下图)

订单详情结构

优惠劵

优惠劵效果图

优惠劵也是由TableView构成的,有两种cell,一种是可以使用的优惠劵,另一种为不可使用的优惠劵,这里通过模型判断cell的展示的类型.

使用规则为H5页面,直接在webView上loadURL就OK了.

我的消息

我的消息效果图

依然是tableView,不过这里会根据用户操作动态改变cell的高度.做法是在给cell设置模型的同时,计算出cell全部展示的高度,在模型中创建辅助参数,保存cell的真实高度以及未打开时的高度,在UITableViewDelegate获取cell的高度时,判断当前cell的状态,根据状态返回对应的高度.

cell内部的显示全部按钮通过闭包回调告诉控制器点击事件,同时将cell的IndexPath作为参数传出来,当用户点击显示全部时,根据当前cell的状态取反,同时tableView.reloadData就动态的改变cell的高度了.

我的收货地址

我的收货地址效果图

额,还是tableView,不说了.

编辑我的地址有两种情况,一种是修改现有的收货地址,进入时对应的选项都已经存在,并且有删除当前地址的View在底部.另外一种是添加新地址,没有参数和删除当前地址view,这里在EditAdressViewController写一个枚举,并且搞一个成员变量type

enum EditAdressViewControllerType: Int {case Addcase Edit
}

在push进入EditAdressViewController时,将EditAdressViewController的类型传入,根据type的类型,显示对应的数据即可.

常见问题

效果如下图:

常见问题效果图

常见问题这里UI的搭建相信读者都了然于心,不过多介绍,只讲一下点击出现动画的逻辑.这里也是一个TableView,常见问题为tableView的headView,详细问题View为tableView的Cell,默认cell的个数为零,当用户点击了headView,记录点击的headView的indexPath.section,刷新tableView,将点击行的Cell个数返回为1,并且单独给这一行的cell返回cell的高度.代码如下

extension HelpDetailViewController: UITableViewDelegate, UITableViewDataSource, HelpHeadViewDelegate {func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {let cell = AnswerCell.answerCell(tableView)cell.question = questions![indexPath.section]return cell}func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {if lastOpenIndex == section && isOpenCell {return 1}return 0}func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {if lastOpenIndex == indexPath.section && isOpenCell {return questions![indexPath.section].cellHeight}return 0}func numberOfSectionsInTableView(tableView: UITableView) -> Int {return questions?.count ?? 0}func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {let headView = tableView.dequeueReusableHeaderFooterViewWithIdentifier("headView") as? HelpHeadViewheadView!.tag = sectionheadView?.delegate = selflet question = questions![section]headView?.question = questionreturn headView!}func headViewDidClck(headView: HelpHeadView) {if lastOpenIndex != -1 && lastOpenIndex != headView.tag && isOpenCell {let headView = questionTableView?.headerViewForSection(lastOpenIndex) as? HelpHeadViewheadView?.isSelected = falselet deleteIndexPaths = [NSIndexPath(forRow: 0, inSection: lastOpenIndex)]isOpenCell = falsequestionTableView?.deleteRowsAtIndexPaths(deleteIndexPaths, withRowAnimation: UITableViewRowAnimation.Automatic)}if lastOpenIndex == headView.tag && isOpenCell {let deleteIndexPaths = [NSIndexPath(forRow: 0, inSection: lastOpenIndex)]isOpenCell = falsequestionTableView?.deleteRowsAtIndexPaths(deleteIndexPaths, withRowAnimation: UITableViewRowAnimation.Automatic)return}lastOpenIndex = headView.tagisOpenCell = truelet insertIndexPaths = [NSIndexPath(forRow: 0, inSection: headView.tag)]questionTableView?.insertRowsAtIndexPaths(insertIndexPaths, withRowAnimation: UITableViewRowAnimation.Top)}}

其他功能

三方分享分享

这里我使用了友盟分享SDK,需要在真机上才可以分享,不过Sina微博需要在后台配置测试账号,大家可能无法测试新浪微博分享~,关于分享也是封装了ShareManager工具类,定义好分享枚举类型

enum ShareType: Int {case WeiXinMyFriend = 1case WeiXinCircleOfFriends = 2case SinaWeiBo = 3case QQZone = 4
}

将弹出的样式也ActionSheet也封装成单个类

class LFBActionSheet: NSObject, UIActionSheetDelegate {private var selectedShaerType: ((shareType: ShareType) -> ())?private var actionSheet: UIActionSheet?func showActionSheetViewShowInView(inView: UIView, selectedShaerType: ((shareType: ShareType) -> ())) {actionSheet = UIActionSheet(title: "分享到",delegate: self, cancelButtonTitle: "取消",destructiveButtonTitle: nil,otherButtonTitles: "微信好友", "微信朋友圈", "新浪微博", "QQ空间")self.selectedShaerType = selectedShaerTypeactionSheet?.showInView(inView)}func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int) {print(buttonIndex)if selectedShaerType != nil {switch buttonIndex {case ShareType.WeiXinMyFriend.rawValue:selectedShaerType!(shareType: .WeiXinMyFriend)breakcase ShareType.WeiXinCircleOfFriends.rawValue:selectedShaerType!(shareType: .WeiXinCircleOfFriends)breakcase ShareType.SinaWeiBo.rawValue:selectedShaerType!(shareType: .SinaWeiBo)breakcase ShareType.QQZone.rawValue:selectedShaerType!(shareType: .QQZone)breakdefault:break}}}}

当外部需要调用分享的时候,只需要调用一句代码即可

     shareActionSheet.showActionSheetViewShowInView(view) { (shareType) -> () inShareManager.shareToShareType(shareType, vc: self)}

扫一扫

扫一扫效果图

注意需要在真机上才可以测试,模拟器没有摄像头的.这里用的iOS7.0以后苹果自带框架AVFoundation,使用非常简单,这也就不过多叙述,讲一下如何实现中间区域亮,四边为黑色的效果,其实原理很简单,在view上创建四个view,如下图

将View的背景色改为黑色,透明度为0.5,添加到View上,搞定.
设置captureMetadataOutput.rectOfInterest的范围,控制扫描区域的敏感范围.

搜索控制器

效果如下

搜索效果图

搜索控制器导航栏上的搜索条使用的UISearchBar,下面为按钮,需要动态的布局按钮的位置,这里有热搜索和历史搜索,考虑到复用性,将搜索View封装成一个View,在便利构造方法中将按钮的名字数组传入,自定在内部布局,计算高度,通过闭包回调将按钮点击的事件通知给控制器,具体代码如下

import UIKitclass SearchView: UIView {private let searchLabel = UILabel()private var lastX: CGFloat = 0private var lastY: CGFloat = 35private var searchButtonClickCallback:((sender: UIButton) -> ())?var searchHeight: CGFloat = 0override init(frame: CGRect) {super.init(frame: frame)searchLabel.frame = CGRectMake(0, 0, frame.size.width - 30, 35)searchLabel.font = UIFont.systemFontOfSize(15)searchLabel.textColor = UIColor.colorWithCustom(140, g: 140, b: 140)addSubview(searchLabel)}required init?(coder aDecoder: NSCoder) {fatalError("init(coder:) has not been implemented")}convenience init(frame: CGRect, searchTitleText: String, searchButtonTitleTexts: [String], searchButtonClickCallback:((sender: UIButton) -> ())) {self.init(frame: frame)searchLabel.text = searchTitleTextvar btnW: CGFloat = 0let btnH: CGFloat = 30let addW: CGFloat = 30let marginX: CGFloat = 10let marginY: CGFloat = 10for i in 0..<searchButtonTitleTexts.count {let btn = UIButton()btn.setTitle(searchButtonTitleTexts[i], forState: UIControlState.Normal)btn.setTitleColor(UIColor.blackColor(), forState: UIControlState.Normal)btn.titleLabel?.font = UIFont.systemFontOfSize(14)btn.titleLabel?.sizeToFit()btn.backgroundColor = UIColor.whiteColor()btn.layer.masksToBounds = truebtn.layer.cornerRadius = 15btn.layer.borderWidth = 0.5btn.layer.borderColor = UIColor.colorWithCustom(200, g: 200, b: 200).CGColorbtn.addTarget(self, action: "searchButtonClick:", forControlEvents: UIControlEvents.TouchUpInside)btnW = btn.titleLabel!.width + addWif frame.width - lastX > btnW {btn.frame = CGRectMake(lastX, lastY, btnW, btnH)} else {btn.frame = CGRectMake(0, lastY + marginY + btnH, btnW, btnH)}lastX = CGRectGetMaxX(btn.frame) + marginXlastY = btn.ysearchHeight = CGRectGetMaxY(btn.frame)addSubview(btn)}self.searchButtonClickCallback = searchButtonClickCallback}func searchButtonClick(sender: UIButton) {if searchButtonClickCallback != nil {searchButtonClickCallback!(sender: sender)}}
}

唠叨一下

关于项目的内容,并不是短短几千文字就能给大家讲明白的,想要了解更多内容,请打开代码仔细研究,小熊还是抱着为了大家能看懂的套路基本没有使用三方框架,应小东同学的要求使用了纯代码开发.希望对大家有所帮助.记着点个Star哈~

最近有点忙,加上整个人回到北京好像变懒了T_T,有段日子没有发布什么资源了.这个项目就作为新年礼物送给大家吧,还有两个小时,开网小熊回家的列车就出发了,在这里提前祝大家新年快乐~

iOS 高仿爱鲜蜂APP相关推荐

  1. swift 高仿爱鲜蜂

    高仿爱鲜蜂 源码github 从swift发布1.0版本时就一直关注着它的成长.目前swift已经比较成熟,可以用来开发完整项目. 现在是时候使用swift练练手了. "爱鲜蜂"是 ...

  2. 企业级旅行App源码、高仿爱鲜蜂源码、iOS Arkit测距源码等

    iOS精选源码 用户发布信息的控件,发布评论,发布图片,发布视频 类似YouTube和脸谱网自定义视频效果 使用ARKit创建的贪吃蛇游戏 iOS ARkit 测量距离 源码 Swift高仿爱鲜蜂 一 ...

  3. 前端微信小程序生鲜类仿爱鲜蜂微信小程序

    需求描述及交互分析 设计思路和相关知识点 首页界面布局设计 闪送超市纵向导航设计 商品添加到购物车设计 购物车商品显示设计 收货地址列表显示设计 新增收货地址设计 交互分析 (1)底部标签导航有首页. ...

  4. Django之爱鲜蜂项目开发 day06(一)

    1支付流程 当购物车商品筛选完毕点击结算按键时,跳转到支付宝支付流程 1.1支付宝支付时序流程图 1.2 沙箱模拟 这里支付并不是支付宝真正的交易,而是使用沙箱模拟交易,一般开发的时候,都是可以先去沙 ...

  5. iOS高仿微信完整源码,网易爱玩APP源码等

    iOS精选源码 iOS一种弹出视图效果带动画 一个快速便捷.无侵入.可扩展的动画弹框库 高仿Elk - 旅行货币转换器 iOS内分享的界面.功能一体化解决方案 使用Olami sdk实现一个语音查询股 ...

  6. 【IOS】高仿暴风视频播放器app源码

    高仿暴风视频播放器app源码 这是一款仿照暴风影音做的demo,因为项目需要,所以顺便把他完善一点,功能有侧滑,滚动导航栏,tableView ,collectionView的高度定制,希望能帮助到有 ...

  7. 爱鲜蜂签约神策数据 让精细化运营落地企业

    数据驱动商业决策与产品优化是企业发展的必然趋势,数据驱动理念逐步在 O2O 行业践行中.O2O 领域知名创新企业爱鲜蜂走在行业前端,于2015年9月选择神策数据,借助神策分析平台帮助产品.运营.分析师 ...

  8. iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...

    2019独角兽企业重金招聘Python工程师标准>>> iOS精选源码 iOS高仿微信完整项目源码 Khala: Swift 编写的iOS/macOS 路由框架 微信左滑删除效果的实 ...

  9. iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码

    iOS精选源码 iOS高仿微信完整项目源码 Khala: Swift 编写的iOS/macOS 路由框架 微信左滑删除效果的实现与TableViewCell的常用样式介绍 实现阴影圆角并存,渐变色背景 ...

最新文章

  1. AI视频行为分析系统项目复盘——技术篇3:tensorRT技术梳理
  2. 不讲武德,拿到户口后立马辞职...
  3. unity项目中使用BUGLY遇到的的几个问题
  4. matlab文件批量导入问题总结
  5. android镜像文件怎么命名,android镜像文件说明(示例代码)
  6. LeetCode 1562. 查找大小为 M 的最新分组
  7. ROS 学习笔记(一):工作空间+功能包创建
  8. linux rdma测试,硬件RDMA的驱动配置和测试
  9. pytorch之各类图像库的图片读写方式
  10. 逆水寒7.25服务器维护,逆水寒7月26日更新维护公告 更新内容汇总
  11. 惊喜:vs2005 和 msdn 中文版 已经提供Subscriber 下载,MSDN全球订户可以下中文版爽了...
  12. Verilog——hdb3编译码的层次化设计与实现
  13. matlab打不开怎么办,matlab打不开_matlab打不开怎么办 matleb打不开的修复方法
  14. BT种子 kitty
  15. Ubuntu20.04安装gamit10.7
  16. Hub设备、网桥、二层交换机设备概述
  17. 限制input 输入框只能输入数字
  18. 热烈欢迎中国照明网总经理丁云高一行莅临新起典考察交流
  19. easyUI的基本知识
  20. 廖雪峰的0Python教程

热门文章

  1. 周六去看《挑战主持人》
  2. CSS的基本概念———每天一遍小知识
  3. 如何合并多个excel中(excel表格样式都一样)
  4. 中国监控电路市场深度研究分析报告
  5. MQTT订阅发布主题
  6. 学校无盘服务器配置,学校无盘服务器配置
  7. 软件工程实验:数据库设计
  8. performSelector多个参数
  9. springboot+基于微信小程序的心理医生系统的设计实现 毕业设计-附源码191610
  10. 出国开会总结,学生,初次出国参加学术会议