仿制新浪微博iOS客户端之四-未登录页面处理
写在最前:非常抱歉,前期因为个人原因有二十多天的时间没有再继续更新这个专题,期间仅仅是完成了苹果官方的UIStackView的文档的翻译。在这里我们将继续未完成的任务,继续做下去!现在继续!
一、前期总结
在完成第三篇的任务后,我们实际上完成的效果如下:
目前我们已经能保证界面的顺利切换,并且给微博的撰写按钮预留了点击事件接口,再下一步,我们将要完成在用户登录前的所有准备工作。
二、抽取基类
目前我们在首页、消息、发现、我这四个界面中使用的都是UITableViewController来加载和显示内容,虽然在实际显示时是有区别的,但是在本质上我们可以认为一样,因此这四个我们可以抽出一个共同的基类,供四个TableViewController来继承,来实现我们想要的功能。
1.创建继承自UITableViewController的类:BaseTVC;
2.修改四个页面对应控制器类的父类;
3.在BaseTVC中写入登录判断代码并测试。
在BaseTVC中写入的代码如下:
// 用户登录标记 var userLogon: Bool = false override func loadView() { // 用户已登录,则执行默认情况 if userLogon { super.loadView() return } // 用户未登录,修改控制器view为空白view view = UIView() }
在用户已登录的情况下,执行的效果与不写这段代码的效果是一样的,因此我们这里只测试未登录的效果,即 userLogon = false。测试结果如下:
在这里我们可以看到,在用户未登录情况下,确实创建了一个空白的view取代了原有的tableView,而这个空白view将是我们实现 用户登录视图 效果的位置。
三、用户登录视图
微博的登录界面基本上如上图所示,中间是一个与页面相关的大图标,界面变化时大图标会进行切换,一圈小图标围绕着大图标转动,并且视图有透明度渐变效果,大图标底部的小图标是有若隐若现的效果的,其他内容则与图片所示的基本一致。
1.首先拖入素材
2.创建xib
由于我们的view是使用代码创建的,因此在storyboard上我们是无法进行操作的,如果要进行处理,我们只有两种方式选择:纯代码操作/xib加载。纯代码开发有一定的可行性,但是考虑到可视化操作对于效率上的优势,我们在这里使用的是xib。
创建xib文件VisitorLoginView,拖入控件并对应设置布局如下:
拖入顺序:小图标图片->遮罩图片->大图标图片->Label->注册按钮、登录按钮
约束顺序:
遮罩与xib左、右、上三边对齐,与下方间距110;
大图标居中,宽高均90;
小图标参照大图标中心对齐,宽高均180;
label参照小图标水平中心对齐,垂直间距8,宽224,高34,字号14,行数2
按钮参照Label分别左右对齐,垂直间距15,宽106,高34,字号18,文字颜色分别为橙色和黑色
备注:默认拖入顺序反映了各个图片之间的遮挡关系,越后拖入的控件,会遮挡住在它之前拖入的图片,因此按照这个顺序,可以保证遮罩是可以遮挡住小图标的,在加上遮罩图片是渐变效果,因此可以显示出一个渐变的小图标图片。
同时,除遮罩外的所有控件的约束都参照了大图标,因此如果大图标移动,则所有控件都会保持当前的相对位置同向移动,这也是方便开发的一种方式。
以上步骤完成后,修改以下代码,再次测试,测试结果如下图:
// view = UIView() view = NSBundle.mainBundle().loadNibNamed("VisitorLoginView", owner: nil, options: nil).last as! UIView
UI设置的部分基本上宣告完成,后续我们只需要根据页面的不同来切换图标和文字内容就可以了。
3.创建自定义view类,对xib文件进行代码补充
创建VisitorLoginView.swift文件,写入以下代码:
@IBOutlet weak var smallIcon: UIImageView! @IBOutlet weak var bigIcon: UIImageView! @IBOutlet weak var msgLabel: UILabel! // 点击注册按钮事件方法 @IBAction func visitorRegister(sender: UIButton) { println(__FUNCTION__) } // 点击登录按钮事件方法 @IBAction func visitorLogin(sender: UIButton) { println(__FUNCTION__) } /** 设置访客界面信息 :param: iconName 显示图标名称 :param: message 信息label内容 :param: isHome 是否首页 */ func setupVisitorInfo(iconName: String, message: String, isHome: Bool = false) { // 非首页情况下:隐藏大图标,将图片显示到小图标 // 首页情况下:大图标显示,并呈现默认图片 bigIcon.hidden = !isHome if isHome { smallIcon.image = UIImage(named: "visitordiscover_feed_image_smallicon") bigIcon.image = UIImage(named: iconName) } else { smallIcon.image = UIImage(named: iconName) } msgLabel.text = message }
在此类中,我们预留了一个接口方法setupVisitorInfo,来设置对应的图片和label的内容。
完成上述内容后,我们需要在各个控制器中调用这个接口,来给不同界面下显示的view进行赋值。
这时就体现出抽取基类的好处了:我们只需要给基类赋予一个属性,那么所有继承基类的类都会自动获得基类所拥有的属性,方法也是如此,因此这里我们先添加一个属性:
// 登录视图属性 var visitorLoginView: VisitorLoginView?
然后将控制器基类的loadView方法进行再次部分修改:
// 用户未登录,修改控制器view为指定xib加载的view visitorLoginView = NSBundle.mainBundle().loadNibNamed("VisitorLoginView", owner: nil, options: nil).last as? VisitorLoginView view = visitorLoginView
最后在四个子控制器的viewDidLoad方法中加入以下代码:
HomePageTableViewController
visitorLoginView?.setupVisitorInfo("visitordiscover_feed_image_house", message: "关注一些人,回这里看看有什么惊喜", isHome: true)
MessageTableViewController
visitorLoginView?.setupVisitorInfo("visitordiscover_image_message", message: "登录后,别人评论你的微博,发给你的消息,都会在这里收到通知")
DiscoverTableViewController
visitorLoginView?.setupVisitorInfo("visitordiscover_image_message", message: "登录后,最新、最热微博尽在掌握,不再会与实事潮流擦肩而过")
ProfileTableViewController
visitorLoginView?.setupVisitorInfo("visitordiscover_image_profile", message: "登录后,你的微博、相册、个人资料会显示在这里,展示给别人")
注意:这里用到了swift语法的一个特性:当一个方法的某个参数被赋予了默认值的情况下,这个参数是可以省略的,省略此参数代表将传入默认值。在这里,我们的三次调用接口就省略了isHome这个参数。
完成以上设置之后,再次运行测试,我们看到的测试结果如下:
下一步,我们将实现首页的小图标转动效果。
4.首页动画效果
在原版的微博中,围绕着大图标的小图标图片是在不停的旋转的,我们在这里也可以做出这样的效果。具体步骤如下:
在自定义xib类中创建动画方法:
// 开始动画 private func startAnimation() { let anim = CABasicAnimation(keyPath: "transform.rotation") anim.repeatCount = MAXFLOAT anim.duration = 20 anim.toValue = M_PI * 2 smallIcon.layer.addAnimation(anim, forKey: "smallIconRotationAnim") }
改写setupVisitorInfo:
func setupVisitorInfo(iconName: String, message: String, isHome: Bool = false) { // 非首页情况下:隐藏大图标,将图片显示到小图标 // 首页情况下:大图标显示,并呈现默认图片 bigIcon.hidden = !isHome if isHome { bigIcon.image = UIImage(named: iconName) if (smallIcon.layer.animationForKey("smallIconRotationAnim") == nil) { startAnimation() } } else { smallIcon.image = UIImage(named: iconName) } msgLabel.text = message }
这里需要提示的几点是:
核心动画中 anim.removedOnCompletion 默认为 true
当视图消失时,动画会被自动从图层删除,因此不需要停止动画方法
在添加动画时,如果指定了 key,图层会对动画强引用,并且不会释放,避免界面切换之后动画停止,不会再启动的情况
因此在以上的代码中,需要对加入的动画设置key,避免界面切换时动画被释放掉。
同时由于一个控制器的viewDidLoad方法只会在视图加载时调用,因此界面切换时是不会调用这个方法的。基于上述原因,HomePageTableViewController中的设置图片类型和文字的方法setupVisitorInfo调整到viewWillAppear方法中。
override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) visitorLoginView?.setupVisitorInfo("visitordiscover_feed_image_house", message: "关注一些人,回这里看看有什么惊喜", isHome: true) }
完成上述步骤后,实现的效果如下:
至此,首页动画效果完成。
5.注册、登陆按钮点击代理
登陆界面中另外要处理的就是注册和登陆按钮的点击了。在这里我们使用代理方式来处理事件的点击。
首先要说明的是,代理的使用是分两部分的:代理的声明以及代理的实现
代理的声明分为三部分:定义代理属性->定义代理协议和方法->在合适时机调用代理方法;
代理的实现同样有三部分:注册代理对象->遵守代理协议->实现代理方法。
5.1 代理声明
不多说,上代码
/** * 定义代理协议及方法 */ protocol VisitorLoginViewDelegate: NSObjectProtocol { func didRegisterButtonClicked() func didLoginButtonClicked() } class VisitorLoginView: UIView { // 定义代理属性 weak var delegate: VisitorLoginViewDelegate? // 点击注册按钮事件方法 @IBAction func visitorRegister(sender: UIButton) { // 在合适时机调用代理方法 delegate?.didRegisterButtonClicked() } // 点击登录按钮事件方法 @IBAction func visitorLogin(sender: UIButton) { // 在合适时机调用代理方法 delegate?.didLoginButtonClicked() }
在这里,我们首先定义的是代理协议及方法,根据代理属性类型定义代理属性,由于代理方法是点击按钮时调用,因此调用代理方法也是在按钮点击事件方法中调用。
5.2 代理实现
在BaseTVC中注册代理对象
// 注册代理对象 visitorLoginView?.delegate = self
遵守代理协议并实现代理方法
extension BaseTVC: VisitorLoginViewDelegate {
func didRegisterButtonClicked() {
println(__FUNCTION__);
}
func didLoginButtonClicked() {
println(__FUNCTION__);
}
}
给导航栏添加两侧的按钮,并注册点击事件:
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "注册", style: UIBarButtonItemStyle.Done, target: self, action: "didRegisterButtonClicked") navigationItem.rightBarButtonItem = UIBarButtonItem(title: "登录", style: UIBarButtonItemStyle.Done, target: self, action: "didLoginButtonClicked")
完成以上代码后,测试结果如下:
6.设置导航栏全局外观
我们代码手动添加的按钮为系统默认样式,是蓝色的按钮,其样式与整个应用的橙、灰配色并不协调,在加上其他页面也可能需要设置外观,因此我们可以设置全局外观,来一次性确定合适的样式。
class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { setupAppearance() return true } // 设置全局外观样式 private func setupAppearance() { UINavigationBar.appearance().tintColor = UIColor.orangeColor() }
完成上述设置后,导航栏按钮也变为了与app色调一致的橙色。
至此,实现用户登录前的所有准备工作便已经完成了。
杂谈:
1.接口
在开发过程中,我们往往会为了一些还未开发的功能和代码预留接口,这是非常必要的手段和方法,也可以理解成这是一个Todo事项,需要我们去完成它。从本项目开始到现在,我们的项目中已经留下了不少的接口(或者说是“坑”),接下来我们会一个个的填上。
2.Swift中的代理
Swift中的代理与OC是基本一致的,实现思路可以说是完全一样,只是有一些细节需要着重说明。
定义协议时,需要继承自:NSObjectProtocol。这是与OC比较大的一个区别。
定义代理属性时,我们使用了weak字段来对属性进行修饰,这样做是为了避免出现循环引用。如果是默认的strong,则控制器会强引用view,当注册代理属性时会强引用view的代理,而view如果再强引用它自身的代理,则一定会出现循环引用。