仿制新浪微博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如果再强引用它自身的代理,则一定会出现循环引用。

posted on 2015-06-30 16:06  Tieria  阅读(1016)  评论(0编辑  收藏  举报

导航