iOS 高仿:花田小憩3.0.1 (下)
⑦ 登录/注册/忘记密码:
眼尖一点的朋友可能在上面的gif中已经发现, 花田小憩中的登录/注册/忘记密码界面几乎是一样的, 我的做法是用一个控制器LoginViewController来代表登录/注册/忘记密码三个功能模块, 通过两个变量isRegister和isRevPwd来判断是哪个功能, 显示哪些界面, 我们点击注册和忘记密码的时候, 会执行代理方法:
// MARK: - LoginHeaderViewDelegate
func loginHeaderView(loginHeaderView: LoginHeaderView,clickRevpwd pwdBtn: UIButton){
letlogin = LoginViewController()
login.isRevPwd = true
navigationController?.pushViewController(login, animated: true)
}
func loginHeaderView(loginHeaderView: LoginHeaderView,clickRegister registerbtn: UIButton){
letlogin = LoginViewController()
login.isRegister = true
navigationController?.pushViewController(login,animated: true)
}
⑧ 验证码的倒计时功能
/// 点击"发送验证码"按钮
func clickSafeNum(btn: UIButton){
varseconds = 10//倒计时时间
letqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
lettimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0,queue);
dispatch_source_set_timer(timer,dispatch_walltime(nil,0),1 * NSEC_PER_SEC,0);//每秒执行
dispatch_source_set_event_handler(timer){
if(seconds<=0){//倒计时结束,关闭
dispatch_source_cancel(timer);
dispatch_async(dispatch_get_main_queue(),{
//设置界面的按钮显示 根据自己需求设置
btn.setTitleColor(UIColor.blackColor(), forState:.Normal)
btn.setTitle("获取验证码", forState:.Normal)
btn.titleLabel?.font = defaultFont14
btn.userInteractionEnabled = true
});
}else{
dispatch_async(dispatch_get_main_queue(),{
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(1)
})
dispatch_async(dispatch_get_main_queue(),{
//设置界面的按钮显示 根据自己需求设置
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(1)
btn.setTitleColor(UIColor.orangeColor(), forState:.Normal)
btn.setTitle("\(seconds)秒后重新发送",forState:.Normal)
btn.titleLabel?.font = UIFont.systemFontOfSize(11)
UIView.commitAnimations()
btn.userInteractionEnabled = false
})
seconds -= 1
}
}
dispatch_resume(timer)
}
⑨ 设置模块中给我们评分
这个功能在实际开发中特别常见:
给我们评分
代码如下, 很简单:
UIApplication.sharedApplication().openURL(NSURL(string:"itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=998252000")!)
其中最后的id需要填写你自己的APP在AppStore中的id, 打开iTunes找到你自己的APP或者你想要的APP, 就能查看到id.
tip: 此功能测试的时候, 必须用真机!!!
⑩ 登录状态.
我们可以通过NSHTTPCookieStorage中的NSHTTPCookie来判断登录状态.也可以自定义一个字段来保存. 根据我抓包得知, 花田小憩APP的做法是第一次登录后保存用户名和密码(MD5加密的, 我测试过), 然后每次启动应用程序的时候, 会首先后台自动登录, 然后在进行评论/点赞等操作的时候呢, 参数中会带上用户的id.由于涉及到花田小憩的账号密码的一些隐私, 所以登录/注册模块, 我就没有没有完整的写出来. 有兴趣的朋友可以私信我, 我可以把接口给你, 在此声明: 仅供学习, 毋做伤天害理之事
`tip: 我在AppDelegate.swift中给大家留了一个开关, 可以快速的进行登录状态的切换…
⑩①: 个人/专栏中心:
这两个功能是同一个控制器, 是UICollectionViewController而不是UITableViewController
大家对UITableViewController的header应该很熟悉吧, 向上滑动的时候, 会停留在navigationBar的下面, 虽然UICollectionViewController也可以设置header, 但是在iOS9以前, 他是不能直接设置停留的.在iOS9之后, 可以一行代码设置header的停留
sectionHeadersPinToVisibleBounds = true
但是在iOS9之前, 我们需要自己实现这个功能:
//
// LevitateHeaderFlowLayout.swift
// Floral
//
// Created by ALin on 16/5/20.
// Copyright © 2016年 ALin. All rights reserved.
// 可以让header悬浮的流水布局
importUIKit
class LevitateHeaderFlowLayout: UICollectionViewFlowLayout{
override func prepareLayout(){
super.prepareLayout()
// 即使界面内容没有超过界面大小,也要竖直方向滑动
collectionView?.alwaysBounceVertical = true
// sectionHeader停留
if#available(iOS 9.0, *) {
sectionHeadersPinToVisibleBounds = true
}
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?{
// 1. 获取父类返回的UICollectionViewLayoutAttributes数组
varanswer = super.layoutAttributesForElementsInRect(rect)!
// 2. 如果是iOS9.0以上, 直接返回父类的即可. 不用执行下面的操作了. 因为我们直接设置sectionHeadersPinToVisibleBounds = true即可
if#available(iOS 9.0, *) {
returnanswer
}
// 3. 如果是iOS9.0以下的系统
// 以下代码来源:http://*.com/questions/13511733/how-to-make-supplementary-view-float-in-uicollectionview-as-section-headers-do-i%3C/p%3E
// 目的是让collectionview的header可以像tableview的header一样, 可以停留
// 创建一个索引集.(NSIndexSet:唯一的,有序的,无符号整数的集合)
letmissingSections = NSMutableIndexSet()
// 遍历, 获取当前屏幕上的所有section
for layoutAttributes inanswer{
// 如果是cell类型, 就加入索引集里面
if(layoutAttributes.representedElementCategory == UICollectionElementCategory.Cell){
missingSections.addIndex(layoutAttributes.indexPath.section)
}
}
// 遍历, 将屏幕中拥有header的section从索引集中移除
for layoutAttributes inanswer{
// 如果是header, 移掉所在的数组
if(layoutAttributes.representedElementKind == UICollectionElementKindSectionHeader){
missingSections.removeIndex(layoutAttributes.indexPath.section)
}
}
// 遍历当前屏幕没有header的索引集
missingSections.enumerateIndexesUsingBlock{(idx,_)in
// 获取section中第一个indexpath
letindexPath = NSIndexPath(forItem:0, inSection: idx)
// 获取其UICollectionViewLayoutAttributes
letlayoutAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath)
// 如果有值, 就添加到UICollectionViewLayoutAttributes数组中去
iflet_ = layoutAttributes{
answer.append(layoutAttributes!)
}
}
// 遍历UICollectionViewLayoutAttributes数组, 更改header的值
for layoutAttributes inanswer{
// 如果是header, 改变其参数
if(layoutAttributes.representedElementKind==UICollectionElementKindSectionHeader){
// 获取header所在的section
letsection = layoutAttributes.indexPath.section
// 获取section中cell总数
letnumberOfItemsInSection = collectionView!.numberOfItemsInSection(section)
// 获取第一个item的IndexPath
letfirstObjectIndexPath = NSIndexPath(forItem:0, inSection: section)
// 获取最后一个item的IndexPath
letlastObjectIndexPath = NSIndexPath(forItem: max(0,(numberOfItemsInSection - 1)), inSection: section)
// 定义两个变量来保存第一个和最后一个item的layoutAttributes属性
var firstObjectAttrs : UICollectionViewLayoutAttributes
var lastObjectAttrs : UICollectionViewLayoutAttributes
// 如果当前section中cell有值, 直接取出来即可
if(numberOfItemsInSection > 0){
firstObjectAttrs =
self.layoutAttributesForItemAtIndexPath(firstObjectIndexPath)!
lastObjectAttrs = self.layoutAttributesForItemAtIndexPath(lastObjectIndexPath)!
}else{// 反之, 直接取header和footer的layoutAttributes属性
firstObjectAttrs = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: firstObjectIndexPath)!
lastObjectAttrs = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionFooter, atIndexPath: lastObjectIndexPath)!
}
// 获取当前header的高和origin
letheaderHeight = CGRectGetHeight(layoutAttributes.frame)
varorigin = layoutAttributes.frame.origin
origin.y = min(// 2. 要保证在即将消失的临界点跟着消失
max(// 1. 需要保证header悬停, 所以取最大值
collectionView!.contentOffset.y +collectionView!.contentInset.top,
(CGRectGetMinY(firstObjectAttrs.frame) - headerHeight)
),
(CGRectGetMaxY(lastObjectAttrs.frame) - headerHeight)
)
// 默认的层次关系是0. 这儿设置大于0即可.为什么设置成1024呢?因为我们是程序猿...
layoutAttributes.zIndex = 1024
layoutAttributes.frame = CGRect(origin: origin, size: layoutAttributes.frame.size)
}
}
returnanswer;
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool{
// 返回true, 表示一旦进行滑动, 就实时调用上面的-layoutAttributesForElementsInRect:方法
returntrue
}
}
⑩+@end:
整个项目, 东西还是蛮多的, 也不是仅仅几百上千字能说清楚的, 几乎每一个页面, 每一个文件, 我都有详细的中文注释. 希望大家一起进步. 这也是我的第一个开源的完整的Swift 项目, 有什么不足或者错误的地方, 希望大家指出来, 万分感激!!!
下载地址
https://github.com/SunLiner/Floral
如果对您有些许帮助, 请☆star
后续
可能有些功能模块存在bug, 后续我都会一一进行修复和完善的, 并更新在github上.
如果您有任何疑问,或者发现bug以及不足的地方, 可以在下面给我留言, 或者关注我的新浪微博, 给我私信.
联系我
https://github.com/SunLiner