04-访客视图

访客视图 - 目标

  • 如果用户没有登录,显示访客视图,提示用户注册或者登录

课程重点

  1. 自定义 TableViewController 的基类 VisitorViewController
  2. 自定义访客视图,用代码实现苹果原生的自动布局
  3. 设置全局外观

应用实例

  • 在实际应用开发中,有可能会出现:
    • 功能框架已经构建完成
    • 产品经理提出新的功能需求
    • 而新提出的功能需求,会对已有的架构产生影响
    • 例如,在新浪微博中,已经搭建好程序架构,但是如何应对用户登录的处理呢?

新的需求 —— 未登录页面

界面截图

  • 首页
首页.png
  • 消息
消息.png
  • 发现
发现.png
我.png

架构分析及调整

  • 现有架构图
现有架构图.png
  • 新增 HMVisitorViewController
新增BassTableVC.png

表格视图控制器基类

功能需求

  • 判断用户是否登录,如果没有登录
    • 使用用户登录视图替换表格视图控制器的默认视图
    • 在导航栏左侧添加 注册 按钮
    • 在导航栏右侧添加 登录 按钮

代码实现

  • 新建 HMVisitorViewController

  • 将功能主界面的视图控制器基类设置为 HMVisitorViewController

    • HMHomeTableViewController
    • HMMessageTableViewController
    • HMDiscoverTableViewController
    • HMProfileTableViewController
  • 增加 用户登录标记,根据用户登录标记判断是否加载默认视图

/// 功能模块控制器的基类控制器
class HMVisitorViewController: UITableViewController {

    /// 用户登录标记
    var userLogon = true

    override func loadView() {
        userLogon ? super.loadView() : setupVisitorView()
    }

    /// 设置访客视图
    private func setupVisitorView() {
        view = UIView()
        view.backgroundColor = UIColor.orangeColor()
    }
}

修改 userLogon 的值,运行测试界面效果

添加导航栏按钮

/// 设置访客视图
private func setupVisitorView() {
    view = UIView()
    view.backgroundColor = UIColor.orangeColor()

    // 添加导航栏按钮
    navigationItem.leftBarButtonItem = UIBarButtonItem(title: "注册", target: nil, action: "")
    navigationItem.rightBarButtonItem = UIBarButtonItem(title: "登录", target: nil, action: "")
}

用户登录视图

对于第三方开发者,新浪没有开放未登录访问数据的权限,因此在用户登录之前,无法 加载微博数据 以及 关注用户

功能需求

  • 用户登录操作视图,用于在用户没有登录时替换表格控制器的根视图
  • 每个功能模块的登录视图包含以下四个控件
    • 模块图标
    • 描述文字
    • 注册按钮
    • 登录按钮
  • 特例
    • 首页有一个小的转轮图片会不停旋转

功能实现

  • 拖拽相关图片素材
  • 新建 HMVisitorView.swift 继承自 UIView
/// 访客登录视图
class HMVisitorView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)

        setupUI()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        setupUI()
    }

    /// 设置界面元素
    private func setupUI() {

    }
}
  • 修改 setupVisitorView 函数
// 替换根视图
view = HMVisitorView()
  • 添加界面元素
/// 设置界面元素
private func setupUI() {
    // 1. 添加控件
    addSubview(circleView)
    addSubview(iconView)
    addSubview(messageLabel)
    addSubview(registerButton)
    addSubview(loginButton)
}

// MARK: - 懒加载属性
// 小房子
private lazy var iconView: UIImageView = UIImageView(image: UIImage(named: "visitordiscover_feed_image_house"))

// 转圈的 view
private lazy var circleView: UIImageView = UIImageView(image: UIImage(named: "visitordiscover_feed_image_smallicon"))

// 提示 label
private lazy var messageLabel: UILabel = {
    // 这个地方使用 extension 里面的便利构造函数初始化的
    let label = UILabel(textColor: UIColor.darkGrayColor(), fontSize: 14)
    label.text = "关注一些人,回这里看看有什么惊喜关注一些人,回这里看看有什么惊喜"
    label.numberOfLines = 0
    // 文本对齐方式
    label.textAlignment = .Center
    return label
}()

// 注册按钮
lazy var registerButton: UIButton = {
    let button = UIButton()
    button.setTitle("注册", forState: .Normal)
    button.setBackgroundImage(UIImage(named: "common_button_white_disable"), forState: .Normal)
    button.titleLabel?.font = UIFont.systemFontOfSize(14)
    button.setTitleColor(UIColor.orangeColor(), forState: .Normal)
    return button
}()

// 登录按钮
lazy var loginButton: UIButton = {
    let button = UIButton()
    button.setTitle("登录", forState: .Normal)
    button.setBackgroundImage(UIImage(named: "common_button_white_disable"), forState: .Normal)
    button.titleLabel?.font = UIFont.systemFontOfSize(14)
    button.setTitleColor(UIColor.darkGrayColor(), forState: .Normal)
    return button
}()

设置自动布局

  • 设置图标约束 - 参照视图居中对齐
// 1> 图标
// 2.1 图标
iconView.translatesAutoresizingMaskIntoConstraints = false
addConstraint(NSLayoutConstraint(item: iconView, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: iconView, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .CenterY, multiplier: 1, constant: 0))
  • 设置首页小房子 - 参照图标居中对齐
// 2> 首页的房子
circleView.translatesAutoresizingMaskIntoConstraints = false
addConstraint(NSLayoutConstraint(item: circleView, attribute: .CenterX, relatedBy: .Equal, toItem: iconView, attribute: .CenterX, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: circleView, attribute: .CenterY, relatedBy: .Equal, toItem: iconView, attribute: .CenterY, multiplier: 1, constant: 0))
  • 设置文本 - 参照图标,水平居中,下方 16 个点
// 3> 描述文字
messageLabel.translatesAutoresizingMaskIntoConstraints = false
addConstraint(NSLayoutConstraint(item: messageLabel, attribute: .CenterX, relatedBy: .Equal, toItem: circleView, attribute: .CenterX, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: messageLabel, attribute: .Top, relatedBy: .Equal, toItem: circleView, attribute: .Bottom, multiplier: 1, constant: 16))

  • 增加文本宽度约束 - 224
// 添加最大宽度约束
addConstraint(NSLayoutConstraint(item: messageLabel, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 224))
  • 注册按钮,文本标签左下(16)对齐,宽度 100,高度 35
// 4> 注册按钮
registerButton.translatesAutoresizingMaskIntoConstraints = false
addConstraint(NSLayoutConstraint(item: registerButton, attribute: .Leading, relatedBy: .Equal, toItem: messageLabel, attribute: .Leading, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: registerButton, attribute: .Top, relatedBy: .Equal, toItem: messageLabel, attribute: .Bottom, multiplier: 1, constant: 16))

addConstraint(NSLayoutConstraint(item: registerButton, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 100))
addConstraint(NSLayoutConstraint(item: registerButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 35))
  • 调整按钮背景图片切片

  • 登录按钮,文本标签右下(16)对齐,宽度 100,高度 35

loginButton.translatesAutoresizingMaskIntoConstraints = false
addConstraint(NSLayoutConstraint(item: loginButton, attribute: .Trailing, relatedBy: .Equal, toItem: messageLabel, attribute: .Trailing, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: loginButton, attribute: .Top, relatedBy: .Equal, toItem: registerButton, attribute: .Top, multiplier: 1, constant: 0))

addConstraint(NSLayoutConstraint(item: loginButton, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 100))
addConstraint(NSLayoutConstraint(item: loginButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 35))
  • 设置登录按钮文字颜色
btn.setTitleColor(UIColor.darkGrayColor(), forState: UIControlState.Normal)
  • 调整控件整体垂直位置
addConstraint(NSLayoutConstraint(item: iconView, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: -60))
  • 添加遮罩图片视图
/// 遮罩视图
private lazy var maskIconView: UIImageView = UIImageView(image: UIImage(named: "visitordiscover_feed_mask_smallicon"))
  • 调整控件的层次
// 1. 添加控件
addSubview(circleView)
addSubview(maskIconView)
addSubview(iconView)
...
  • 遮罩图片自动布局
// 6> 遮罩视图
maskIconView.translatesAutoresizingMaskIntoConstraints = false
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[subview]-0-|", options: [], metrics: nil, views: ["subview": maskIconView]));
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[subview]-(-35)-[regButton]", options: [], metrics: nil, views: ["subview": maskIconView, "regButton": registerButton]));
  • 视图背景颜色
// 3. 设置视图背景颜色
backgroundColor = UIColor(white: 237.0 / 255.0, alpha: 1.0)

运行测试

设置未登录信息

  • 设置访客视图信息
/**
设置各个页签信息

- parameter imageName: 图片名字
- parameter message:   信息内容
*/
func setupInfo(imageName: String?, message: String?) {
    if imageName != nil {
        circleView.hidden = true
        iconView.image = UIImage(named: imageName!)
        messageLabel.text = message
    }
}
  • HMVisitorViewController 中添加登录视图属性
private lazy var visitorView: HMVisitorView = {
    let visitorView = HMVisitorView()
    return visitorView
}()
  • setupVisitorView 中记录登录视图
view = visitorView

修改功能视图控制器中的代码

  • HMHomeTableViewController
if !userLogon {
    visitorView.setupInfo(nil, message: nil)
    return
}
  • HMMessageTableViewController
if !userLogon {
    visitorView.setupInfo("visitordiscover_image_message", message: "登录后,别人评论你的微博,发给你的消息,都会在这里收到通知")
    return
}
  • HMDiscoverTableViewController
if !userLogon {
    visitorView.setupInfo("visitordiscover_image_message", message: "登录后,最新、最热微博尽在掌握,不再会与实事潮流擦肩而过")
    return
}
  • HMProfileTableViewController
if !userLogon {
    visitorView.setupInfo("visitordiscover_image_profile", message: "登录后,你的微博、相册、个人资料会显示在这里,展示给别人")
    return
}
  • 提示信息
    • 关注一些人,回这里看看有什么惊喜
    • 登录后,别人评论你的微博,发给你的消息,都会在这里收到通知
    • 登录后,最新、最热微博尽在掌握,不再会与实事潮流擦肩而过
    • 登录后,你的微博、相册、个人资料会显示在这里,展示给别人

首页动画

  • 添加动画代码
/// 启动动画
/**
首页的动画
*/
private func startAnim(){
    let anim = CABasicAnimation(keyPath: "transform.rotation")
    // 旋转
    anim.toValue = 2 * M_PI
    // 执行时间
    anim.duration = 20
    // 执行次数
    anim.repeatCount = MAXFLOAT
    // 切换界面的时候动画会被释放,指定为false之后切换界面动画就不会被释放
    anim.removedOnCompletion = false
    // 添加动画
    circleView.layer.addAnimation(anim, forKey: nil)
}
  • 调整 setupInfo 函数
/// 设置访客视图信息
///
/// - parameter imageName: 图片名称
/// - parameter message:   消息文字
func setupInfo(imageName: String?, message: String?){
    if imageName == nil {
        circleView.hidden = false
        startAnim()
    }else{
        circleView.hidden = true
        iconView.image = UIImage(named: imageName!)
        messageLabel.text = message
    }
}

运行测试,发现切换控制器后动画会被释放,另外在首页退出到桌面再次进入,动画同样会被释放

  • 设置动画属性
anim.removedOnCompletion = false

登录&注册代理回调

  • 定义协议
/// 访客登录视图协议
protocol HMVisitorViewDelegate: NSObjectProtocol{
    /// 访客视图将要登录
    func visitorLoginViewWillLogin();
    /// 访客视图将要注册
    func visitorLoginViewWillRegister();
}

定义协议时,需要继承自 NSObjectProtocol,否则无法设置代理的属性为 weak

  • 定义代理
weak var delegate: HMVisitorViewDelegate?
  • 按钮回调
// MARK: - 监听按钮点击
@objc private func registerButtonClick(){
    delegate?.visitorLoginViewWillRegister()
}

@objc private func loginButtonClick(){
    delegate?.visitorLoginViewWillLogin()
}
  • 遵守协议
class HMVisitorViewController: UITableViewController, VisitorLoginViewDelegate
  • 设置代理
visitorView.delegte = self
  • 实现方法
// MARK: - VisitorLoginViewDelegate
func visitorLoginViewWillLogin() {
    print("登录")
}

func visitorLoginViewWillRegister() {
    print("注册")
}
  • 修改导航条按钮监听方法
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "注册", style: UIBarButtonItemStyle.Plain, target: self, action: "visitorLoginViewWillRegister")
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "登录", style: UIBarButtonItemStyle.Plain, target: self, action: "visitorLoginViewWillLogin")

运行测试

登录&注册按钮监听

  • 修改 HMVisitorViewController
    • 删除遵守协议
    • 删除设置代理属性
  • 修改 VisitorLoginView
    • 删除协议
    • 删除 delegate 属性
    • 删除按钮监听方法
    • 取消 注册 & 登录 按钮的 private 修饰符
  • setupVisitorView 方法中添加按钮监听方法
// 设置按钮监听方法
visitorView.registerButton.addTarget(self, action: "visitorLoginViewWillRegistor", forControlEvents: UIControlEvents.TouchUpInside)
visitorView.loginButton.addTarget(self, action: "visitorLoginViewWillLogin", forControlEvents: UIControlEvents.TouchUpInside)
  • 修改按钮监听方法作用域
// MARK: - 按钮监听方法
@objc private func visitorLoginViewWillLogin() {
    print("登录")
}

@objc private func visitorLoginViewWillRegistor() {
    print("注册")
}

阶段性小结

应用程序设计

  • 程序开发过程中,如果因为需求变化要对应用程序做大幅度调整,以对现有代码做最小化修改为原则,可以考虑抽取基类的方式实现
  • 整理项目目录结构时,不建议将 AppDelegate 隐藏

代理的使用

  • swift 中代理的使用基本与 OC 相同
  • 需要注意的是,定义协议时,需要继承自 NSObjectProtocol
  • 代理属性需要使用 weak 防止出现循环引用

事件传递

  • 可以直接在控制器中添加自定义视图中的按钮监听方法
  • 在 Swift 中可以直接将按钮开放
  • 在 OC 中可以将按钮的定义放在 .h
  • 在 Storyboard 中,可以直接拖线
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容