iOS 高仿爱鲜蜂APP
//联系人:石虎 QQ: 1224614774昵称:嗡嘛呢叭咪哄
iOS高仿爱鲜蜂
前言
2016年匆匆的就过去了,又老了一岁,这一年起起伏伏,有笑声也有眼泪,感谢陪伴在我身边的人.
关于项目(代码下载地址在文章最下面点击GitHub链接)
本次开源项目为爱鲜蜂,一款电商APP,使用语言Swift2.0,开发工具Xcode7.0.1.
项目为纯代码开发,没有使用XIB和StoryBoard.开发周期大概为2个月左右(工作闲暇之余).
数据都是本地数据,辅助开发软件:PhotoShop CS6(图片处理),Charles(抓包工具).
写的比较匆忙,很多地方无法尽善尽美,如果有建议和可优化的地方可在文章底部留言,我会一一查看的并回复的.
项目效果图
项目详细讲解(根据启动流程)
引导页和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的动画(效果如下图)
底部的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(如图所示)
通过判断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相关推荐
- swift 高仿爱鲜蜂
高仿爱鲜蜂 源码github 从swift发布1.0版本时就一直关注着它的成长.目前swift已经比较成熟,可以用来开发完整项目. 现在是时候使用swift练练手了. "爱鲜蜂"是 ...
- 企业级旅行App源码、高仿爱鲜蜂源码、iOS Arkit测距源码等
iOS精选源码 用户发布信息的控件,发布评论,发布图片,发布视频 类似YouTube和脸谱网自定义视频效果 使用ARKit创建的贪吃蛇游戏 iOS ARkit 测量距离 源码 Swift高仿爱鲜蜂 一 ...
- 前端微信小程序生鲜类仿爱鲜蜂微信小程序
需求描述及交互分析 设计思路和相关知识点 首页界面布局设计 闪送超市纵向导航设计 商品添加到购物车设计 购物车商品显示设计 收货地址列表显示设计 新增收货地址设计 交互分析 (1)底部标签导航有首页. ...
- Django之爱鲜蜂项目开发 day06(一)
1支付流程 当购物车商品筛选完毕点击结算按键时,跳转到支付宝支付流程 1.1支付宝支付时序流程图 1.2 沙箱模拟 这里支付并不是支付宝真正的交易,而是使用沙箱模拟交易,一般开发的时候,都是可以先去沙 ...
- iOS高仿微信完整源码,网易爱玩APP源码等
iOS精选源码 iOS一种弹出视图效果带动画 一个快速便捷.无侵入.可扩展的动画弹框库 高仿Elk - 旅行货币转换器 iOS内分享的界面.功能一体化解决方案 使用Olami sdk实现一个语音查询股 ...
- 【IOS】高仿暴风视频播放器app源码
高仿暴风视频播放器app源码 这是一款仿照暴风影音做的demo,因为项目需要,所以顺便把他完善一点,功能有侧滑,滚动导航栏,tableView ,collectionView的高度定制,希望能帮助到有 ...
- 爱鲜蜂签约神策数据 让精细化运营落地企业
数据驱动商业决策与产品优化是企业发展的必然趋势,数据驱动理念逐步在 O2O 行业践行中.O2O 领域知名创新企业爱鲜蜂走在行业前端,于2015年9月选择神策数据,借助神策分析平台帮助产品.运营.分析师 ...
- iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...
2019独角兽企业重金招聘Python工程师标准>>> iOS精选源码 iOS高仿微信完整项目源码 Khala: Swift 编写的iOS/macOS 路由框架 微信左滑删除效果的实 ...
- iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码
iOS精选源码 iOS高仿微信完整项目源码 Khala: Swift 编写的iOS/macOS 路由框架 微信左滑删除效果的实现与TableViewCell的常用样式介绍 实现阴影圆角并存,渐变色背景 ...
最新文章
- AI视频行为分析系统项目复盘——技术篇3:tensorRT技术梳理
- 不讲武德,拿到户口后立马辞职...
- unity项目中使用BUGLY遇到的的几个问题
- matlab文件批量导入问题总结
- android镜像文件怎么命名,android镜像文件说明(示例代码)
- LeetCode 1562. 查找大小为 M 的最新分组
- ROS 学习笔记(一):工作空间+功能包创建
- linux rdma测试,硬件RDMA的驱动配置和测试
- pytorch之各类图像库的图片读写方式
- 逆水寒7.25服务器维护,逆水寒7月26日更新维护公告 更新内容汇总
- 惊喜:vs2005 和 msdn 中文版 已经提供Subscriber 下载,MSDN全球订户可以下中文版爽了...
- Verilog——hdb3编译码的层次化设计与实现
- matlab打不开怎么办,matlab打不开_matlab打不开怎么办 matleb打不开的修复方法
- BT种子 kitty
- Ubuntu20.04安装gamit10.7
- Hub设备、网桥、二层交换机设备概述
- 限制input 输入框只能输入数字
- 热烈欢迎中国照明网总经理丁云高一行莅临新起典考察交流
- easyUI的基本知识
- 廖雪峰的0Python教程