iOS 高仿:花田小憩3.0.1 (上)
前言
断断续续的已经学习Swift一年多了, 从1.2到现在的2.2, 一直在语法之间徘徊, 学一段时间, 工作一忙, 再捡起来隔段时间又忘了.思来想去, 趁着这两个月加班不是特别多, 就决定用swift仿写一个完整项目.
花田小憩:是一个植物美学生活平台,
以自然生活为主导,
提倡植物学生活方法,
倡导美学标准的生活态度的一个APP.
个人文字功底有限, 就我而言, 这款APP做的挺唯美的…
github地址
https://github.com/SunLiner/Floral
声明
此花田小憩项目里面的都是真实接口, 真实数据, 仅供学习, 毋作其他用途!!!
项目部分截图
由于项目的大体功能都已经实现了的, 所以整个项目还是比较庞大的.所以, 下面罗列部分功能的截图.
由于gif录制的时候, 会重新渲染一遍图片, 所以导致项目中用到高斯模糊的地方, 看起来感觉比较乱, 实际效果还是不错的.
新特性
详情页
更多项目截图请点击原文查看
项目环境
编译器 : Xcode7.3及以上
语言 : Swift2.2
整个项目都是采用纯代码开发模式
tip: 之前编译环境这儿有点错误, 因为我项目中用了Swift2.2的特性, 2.2之后方法名需要写成#selector(AddAddressViewController.save), 不再使用双引号了
第三方框架
use_frameworks!
platform:ios,"8.0"
target'Floral'do
pod'SnapKit','~> 0.20.0'## 自动布局
pod'Alamofire','~> 3.3.1'## 网络请求, swift版的AFN
pod'Kingfisher','~> 2.3.1'## 轻量级的SDWebImage
end
还用到了MBProgressHUD.
除此之外,几乎全部都是自己造的小*…
目录结构详解
Classes下包含7个功能目录:
①Resources : 项目用到的资源,包含plist文件, js文件和字体
②Network : 网络请求, 所有的网络请求都在这里面, 接口和参数都有详细的注释
③Tool : 包含tools(工具类), 3rdLib(第三方:友盟分享, MBProgressHUD ), Category(所有项目用到的分类)
④Home : 首页(专题), 包含专题分类, 详情, 每周Top10, 评论, 分享等等功能模块
⑤Main : UITabBarController, UINavigationController设置以及新特性
⑥Malls : 商城, 包含商城分类, 商品搜索, 详情, 购物车, 购买, 订单, 地址管理, 支付等等功能模块
⑦Profile : 个人中心, 专栏作者, 登录/注册/忘记密码, 设置等功能模块
大家可以下载项目, 对照这个目录结构进行查看, 很典型的MVC文件结构, 还是很方便的.
项目部分功能模块详解
① 新特性NewFeatureViewController : 这个功能模块还是比较简单的, 用到了UICollectionViewController, 然后自己添加了UIPageControl, 只需要监听最后一个cell的点击即可.
这儿有一个注意点是: 我们需要根据版本号来判断是进入新特性界面, 广告页还是首页.
private let SLBundleShortVersionString = "SLBundleShortVersionString"
// MARK: - 判断版本号
private func toNewFeature() -> Bool
{
// 根据版本号来确定是否进入新特性界面
letcurrentVersion = NSBundle.mainBundle().infoDictionary!["CFBundleShortVersionString"] as!String
letoldVersion = NSUserDefaults.standardUserDefaults().objectForKey(SLBundleShortVersionString)??""
// 如果当前的版本号和本地保存的版本比较是降序, 则需要显示新特性
if(currentVersion.compare(oldVersionas!String)) == .OrderedDescending{
// 保存当前的版本
NSUserDefaults.standardUserDefaults().setObject(currentVersion,forKey: SLBundleShortVersionString)
returntrue
}
returnfalse
}
② 下拉刷新RefreshControl : 在这个项目中, 没有用第三方的下拉刷新控件, 而是自己实现了一个简单的下拉刷新*, 然后赋值给UITableViewController的public var refreshControl: UIRefreshControl?属性. 主要原理就是判断下拉时的frame变化:
// 监听frame的变化
addObserver(self,forKeyPath:"frame",options:.New,context: nil)
// 刷新的时候, 不再进行其他操作
private var isLoading = false
override func observeValueForKeyPath(keyPath: String?,ofObject object: AnyObject?, change:[String: AnyObject]?, context: UnsafeMutablePointer<Void>){
lety = frame.origin.y
// 1. 最开始一进来的时候, 刷新按钮是隐藏的, y就是-64, 需要先判断掉, y>=0 , 说明刷新控件已经完全缩回去了...
ify >= 0 || y == -64
{
return
}
// 2. 判断是否一进来就进行刷新
ifbeginAnimFlag && (y == -60.0 || y == -124.0){
if !isLoading{
isLoading = true
animtoringFlag = true
tipView.beginLoadingAnimator()
}
return
}
// 3. 释放已经触发了刷新事件, 如果触发了, 需要进行旋转
ifrefreshing && !animtoringFlag
{
animtoringFlag = true
tipView.beginLoadingAnimator()
return
}
ify <= -50 && !rotationFlag
{
rotationFlag = true
tipView.rotationRefresh(rotationFlag)
}elseif(y > -50 && rotationFlag){
rotationFlag = false
tipView.rotationRefresh(rotationFlag)
}
}
③ 高斯模糊: 使用的是系统自带的高斯模糊控件UIVisualEffectView, 它是@available(iOS 8.0, *), 附一段简单的使用代码
private lazy var blurView : BlurView = {
letblur = BlurView(effect: UIBlurEffect(style:.Light))
blur.categories = self.categories
blur.delegate = self
returnblur
}()
可以根据alpha = 0.5, 调整alpha来调整模糊效果, gif图中的高斯模糊效果不是很明显, 实际效果特别好.
④ 商城购物车动画:这组动画还是比较简单的, 直接附代码, 如果有什么疑惑, 可以留言或者私信我
// MARK : - 动画相关懒加载
/// layer
private lazy var animLayer : CALayer = {
letlayer = CALayer()
layer.contentsGravity = kCAGravityResizeAspectFill;
layer.bounds = CGRectMake(0,0,50,50);
layer.cornerRadius = CGRectGetHeight(layer.bounds) / 2
layer.masksToBounds = true;
returnlayer
}()
/// 贝塞尔路径
private lazy var animPath = UIBezierPath()
/// 动画组
private lazy var groupAnim : CAAnimationGroup = {
letanimation = CAKeyframeAnimation(keyPath:"position")
animation.path = self.animPath.CGPath
animation.rotationMode = kCAAnimationRotateAuto
letexpandAnimation = CABasicAnimation(keyPath:"transform.scale")
expandAnimation.duration = 1
expandAnimation.fromValue = 0.5
expandAnimation.toValue = 2
expandAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
letnarrowAnimation = CABasicAnimation(keyPath:"transform.scale")
// 先执行上面的, 然后再开始
narrowAnimation.beginTime = 1
narrowAnimation.duration = 0.5
narrowAnimation.fromValue = 2
narrowAnimation.toValue = 0.5
narrowAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
letgroups = CAAnimationGroup()
groups.animations = [animation,expandAnimation,narrowAnimation]
groups.duration = 1.5
groups.removedOnCompletion = false
groups.fillMode = kCAFillModeForwards
groups.delegate = self
returngroups
}()
// MARK: - 点击事件处理
private var num = 0
func gotoShopCar(){
ifnum >= 99{
self.showErrorMessage("亲, 企业采购请联系我们客服")
return
}
addtoCar.userInteractionEnabled = false
// 设置layer
// 贝塞尔弧线的起点
animLayer.position = addtoCar.center
layer.addSublayer(animLayer)
// 设置path
animPath.moveToPoint(animLayer.position)
letcontrolPointX = CGRectGetMaxX(addtoCar.frame) * 0.5
// 弧线, controlPoint基准点, endPoint结束点
animPath.addQuadCurveToPoint(shopCarBtn.center, controlPoint: CGPointMake(controlPointX, -frame.size.height*5))
// 添加并开始动画
animLayer.addAnimation(groupAnim, forKey:"groups")
}
// MARK: - 动画的代理
// 动画停止的代理
override func animationDidStop(anim: CAAnimation,finished flag: Bool){
ifanim == animLayer.animationForKey("groups")!{
animLayer.removeFromSuperlayer()
animLayer.removeAllAnimations()
num += 1
shopCarBtn.num = num
letanimation = CATransition()
animation.duration = 0.25
shopCarBtn.layer.addAnimation(animation,forKey: nil)
letshakeAnimation = CABasicAnimation(keyPath:"transform.translation.y")
shakeAnimation.duration = 0.25
shakeAnimation.fromValue = -5
shakeAnimation.toValue = 5
shakeAnimation.autoreverses = true
shopCarBtn.layer.addAnimation(shakeAnimation,forKey: nil)
addtoCar.userInteractionEnabled = true
}
}
⑤ 主题详情页:商城详情页的做法也是差不多的, 不过更简单一点.
关键一点在于, 详情页的展示主要依靠于H5页面. 而我们需要根据webview的高度来确定webviewCell的高度.我的做法是监听UIWebView的webViewDidFinishLoad, 取出webView.scrollView.contentSize.height然后给详情页发送一个通知, 让其刷新界面. 暂时没有想到更好的方法, 如果您有更好的做法, 请务必告诉我, 谢谢…
⑥ UIWebView中图片的点击
第①步: 我们创建一个image.js文件, 代码如下:
//setImage的作用是为页面的中img元素添加onClick事件,即设置点击时调用imageClick
function setImageClick(){
varimgs = document.getElementsByTagName("img");
for(vari=0;i<imgs.length;i++){
varsrc = imgs[i].src;
imgs[i].setAttribute("onClick","imageClick(src)");
}
document.location = imageurls;
}
//imageClick即图片 onClick时触发的方法,document.location = url;的作用是使调用
//webView: shouldStartLoadWithRequest: navigationType:方法,在该方法中我们真正处理图片的点击
function imageClick(imagesrc){
varurl="imageClick::"+imagesrc;
document.location = url;
}
第②步:在UIWebView的代理方法webViewDidFinishLoad中, 加载JS文件, 并给图片绑定绑定点击事件
// 加载js文件
webView.stringByEvaluatingJavaScriptFromString(try!String(contentsOfURL: NSBundle.mainBundle().URLForResource("image",withExtension:"js")!,encoding: NSUTF8StringEncoding))
// 给图片绑定点击事件
webView.stringByEvaluatingJavaScriptFromString("setImageClick()")
第③步:在UIWebView的代理方法-webView:shouldStartLoadWithRequest:navigationType:中判断图片的点击
func webView(webView: UIWebView,shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool{
leturlstr = request.URL?.absoluteString
let components :[String] = urlstr!.componentsSeparatedByString("::")
if(components.count >= 1){
//判断是不是图片点击
if(components[0] == "imageclick"){
parentViewController?.presentViewController(ImageBrowserViewController(urls:[NSURL(string: components.last!)!], index: NSIndexPath(forItem:0, inSection:0)), animated: true, completion: nil)
returnfalse;
}
returntrue;
}
returntrue
}