ipad版Qzone(纯swift编写)

一 app大致框架图 : (竖屏)

二 app大致框架图 : (横屏)

三 知识点

1 自动布局(熟悉)

2 协议(熟悉)

3 横屏和竖屏的配置(掌握)

4 细节处理

四 布局登录界面和处理业逻辑

1 自动布局效果图

2 账号和密码相关设置

—-> 2.1 账号

—-> 2.2 密码

3 创建一个登录控制器,用来处理登录模块相关业务逻辑

4 该部分需要的相关属性(通过从storyboard中拖线得到)

class XFJLoginViewController: XFJBaseViewController {

    /// 登录时候转圈提示
    @IBOutlet weak var activityView: UIActivityIndicatorView!
    /// 登录的view
    @IBOutlet weak var loginView: UIView!
    /// 密码文本
    @IBOutlet weak var passWordTextField: UITextField!
    /// 账号文本
    @IBOutlet weak var countTextField: UITextField!
    //自动登录
    @IBOutlet weak var autoLogin: UIButton!
    //记住密码
    @IBOutlet weak var remberPassword: UIButton!
}

5 处理账号和密码登陆的业务逻辑

—-> 5.1 isEmpty:用来判断账号和密码是否为空
——–> 5.1.1 获取用户输入的账号和密码
——–> 5.1.2 模拟用户登录效果(通过延迟执行达到效果)
——–> 5.1.3 显示用户账号正在登录的提示(UIActivityIndicatorView类)
—-> 5.2 用字符串相等来判断是否输入正确

该部分业务逻辑实现代码 :

//MARK: - 判断账号和密码是否为空和密码错误
extension XFJLoginViewController {
    @IBAction func loginBtnClick() { //通过对按钮拖线
        //获取账号和密码
        let countTF = countTextField.text
        let passwordTF = passWordTextField.text
        //判断内容为空是否登录
        if countTF?.isEmpty == true || passwordTF?.isEmpty == true {

            showError("密码或账号不能为空")
            //为空的话,后面就没必要执行了
            return
        }
        //判断账号和面是否正确
        activityView.startAnimating()
        //转圈的时候停止交互
        view.userInteractionEnabled = false
        //延迟执行
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC)), dispatch_get_main_queue(), { () -> Void in
            //停止转圈
            self.activityView.stopAnimating()
            //允许交互
            self.view.userInteractionEnabled = true
            //判断账号和密码是否正确
            if countTF == "123" && passwordTF == "123" {
            //登录成功跳转下一个界面(这里采用修改根控制器的方法实现)
            self.view.window?.rootViewController = XFJHomeViewController()
            }else {
                self.showError("账号或密码错误")
            }
        })
    }
}

6 提示框和登录的时候账号和密码一起抖动情况

—-> 6.1 用UIAlertController这个类来达到提示用户是否登录成功
—-> 6.2 抖动效果(由于账号;密码;登录按钮;记住密码;自动登录在布局的时候是用一个UIView来作为父控制器,如果不是这样是无法达到同时抖动的效果)

该部分业务逻辑实现代码 : (考虑到该部分代码比较多,所以写在类扩展的方法中)

//MARK: - 处理登录的抖动情况
extension XFJLoginViewController {
    //登录时候的抖动效果和弹框提示
    private func showError (messaage : String) {
        let alertControler = UIAlertController(title: "登录失败", message: messaage, preferredStyle: .Alert)
        let alertAction = UIAlertAction(title: "确认", style: .Cancel, handler: nil)
        //添加
        alertControler.addAction(alertAction)
        //弹出框
        presentViewController(alertControler, animated: true, completion: nil)
        //登录时候的抖动效果
        let shakeAnimted = CAKeyframeAnimation(keyPath: "transform.translation.x")
        shakeAnimted.repeatCount = 3
        shakeAnimted.duration = 0.1
        shakeAnimted.values = [-10,0,10]
        loginView.layer.addAnimation(shakeAnimted, forKey: nil)
    }
}

7 自动登录和记住密码

—-> 7.1 该部分业务逻辑介绍 :
——> 7.1.1 当记住密码被用户选中,那么自动登录也需要选中
——> 7.1.2 当记住密码由选中到取消,那么自动登录也需要自动选中
——> 7.1.3 当自动登录被选中,那么记住密码也需要自动选中

记住密码业务逻辑代码块 :

//MARK: - 记住密码
extension XFJLoginViewController {
    @IBAction func remberBtnClick(button : UIButton) { //需要通过连线拿到
        //反选中
        button.selected = !button.selected
        if button.selected == false {
            autoLogin.selected = false
        }
    }
}

自动登录业务逻辑代码块 :

//MARK: - 自动登录
extension XFJLoginViewController {
    @IBAction func automaticLogin(button : UIButton) {  //需要通过连线拿到
        //反选中
        button.selected = !button.selected
        if button.selected == true {
            remberPassword.selected = true
        }
    }
}

8 处理键盘上的NEXT和Down逻辑

—-> 8.1 处理思想 : 代理
—-> 8.2 业务逻辑介绍 : 1> 判断账号是否正确,如果正确点击NEXT跳入密码输入框 2> 判断密码是否正确,如果正确点击Down跳入主界面
—-> 8.3 问题 : 如何给键盘设置代理呢?
—-> 8.4 解决方法 : 在storyboard中通过连线解决(账号和密码输入框同理)

—-> 8.5 实现代理方法,在代理方法中实现相关业务逻辑
//对键盘的处理(NEXT和Down)----> 需要在storyboard中设置代理否则是下面代码是没用的
extension XFJLoginViewController : UITextFieldDelegate {
     func textFieldShouldReturn(textField: UITextField) -> Bool {
        //判断
        if  textField == countTextField {
            //如果判断正确,让密码键盘成为第一响应者
            passWordTextField.becomeFirstResponder()
        }else if passWordTextField == textField {
            //如果密码相等,调用登录方法
            loginBtnClick()
        }
        return true
    }
}

五 处理控制器view的状态栏颜色

1 思路 : 重写系统设置状态栏的函数,将状态栏的颜色改为自己想要的,由于主界面的控制器也需要这种状态,那么我们的思想是创建一个控制器,在该类中设置状态栏样式,然后再创建一个主界面控制器,继承第一个控制.

2 用来设置状态栏的控制器

3 处理代码 :

class XFJBaseViewController: UIViewController {
    //设置状态栏的样式
    override func preferredStatusBarStyle() -> UIStatusBarStyle {
        return .LightContent
    }
}

六 主界面

1 创建一个继承XFJBaseViewController(主控制器也需要设置状态栏样式)的控制器.用来作为主界面控制器

2 创建左侧的工具栏(dock)是一个继承系统的UIView,用来管理底部,中间,顶部按钮

3 在XFJHomeViewController控制器中创建dock(一般是懒加载)

该部分代码 :

//懒加载
    private lazy var dock : XFJDock = {
        //创建dock
        let dock = XFJDock()
        //添加dock到view中
        self.view.addSubview(dock)
        //设置高度
        dock.frame.size.height = self.view.frame.height
        //设置autoresizing跟随父控件的拉伸而拉伸
        dock.autoresizingMask = .FlexibleHeight
        return dock
    }()

4 创建底部工具栏

—-> 4.1 注意 : 1> 代码书写位置 : XFJDock ; 2>采用方式 : 懒加载
//懒加载底部工具栏
    private lazy var bottomMenu : XFJBottom = {
        //创建底部工具栏
        let bottomMenu = XFJBottom()
        //将创建好的底部工具条添加到view中
        self.addSubview(bottomMenu)
        //设置子控件跟随父控件的顶部拉伸而拉伸
        bottomMenu.autoresizingMask = .FlexibleTopMargin
        return bottomMenu
    }()

5 整个文件中需要用到的参数

—-> 5.1 创建一个文件用来设置整个文件需要用到的参数

—-> 5.2 该文件中需要的参数
//横屏
let kDockLaspaceWidth : CGFloat = 270.0
//竖屏
let kDockProtraitWidth : CGFloat = 70.0

//item的高度
let kDockLaspaceHeight : CGFloat = 70.0

//头像横屏时候的高度
let kIconButtonLaspaceHeight : CGFloat = kIconButtonLaspaceWidth + kIconTextHeight

//头像横屏时候的宽度
let kIconButtonLaspaceWidth : CGFloat = 90.0

//头像竖屏时候的高度和宽度
let kIconProtraitWH : CGFloat = 60

//头像横屏和竖屏的时候y值一样
let kIconLpaceY : CGFloat = 40.0

//头像文字的高度
let kIconTextHeight : CGFloat = 30.0

6 处理屏幕旋转过程中dock中的各部分工具模块

—-> 6.1 通过在XFJHomeViewController重写系统的方法,在该方法中将屏幕的,通过在该方法中让屏幕的宽度和高度比较,将计较的值传入各个工具模块中
//当屏幕即将旋转的时候调用,其实并没有旋转
    override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        //判断当前屏幕的true还是false
        let isLaspace = size.width > size.height
        //设置dock的宽度
        let duration = coordinator.transitionDuration()
        UIView.animateWithDuration(duration) { () -> Void in
            //将屏幕方向传入dock中
            self.dock.roteLspaces(isLaspace)
            //更新旋转后的x值
            self.contentive?.frame.origin.x = self.dock.frame.width
        }
    }
—-> 6.2 解析 : 该方法中通过self.dock.roteLspaces(isLaspace)将判断得出的结果传入dock中,在dock中再做相应的处理

7 在XFJDock中定义一个方法,用来接收self.dock.roteLspaces(isLaspace)中的isLaspace值,同时作为参数,传到每个模块中,通过这个值来计算各个模块在屏幕不同的方向时候各模块显示的样式.

七 底部模块

1 在XFJDock类中创建XFJBottom(底部模块对象)

2 采用创建方式 : 懒加载(用到的时候再加载)

//懒加载底部工具栏
    private lazy var bottomMenu : XFJBottom = {
        //创建底部工具栏
        let bottomMenu = XFJBottom()
        //将创建好的底部工具条添加到view中
        self.addSubview(bottomMenu)
        //设置子控件跟随父控件的顶部拉伸而拉伸
        bottomMenu.autoresizingMask = .FlexibleTopMargin
        return bottomMenu
    }()

3 在XFJBottom类中布局屏幕旋转的尺寸和添加按钮

—-> 3.1 根据从XFJDock类中的方法中传入的isLaspace参数来布局底部模块的尺寸
//提供一个方法,让外界将屏幕当前的状态传进来
    func rateLaspace(isLaspace : Bool) {
        //取出按钮总数
        let count = subviews.count
        //设置自身的宽度
        self.frame.size.width = (superview?.frame.width)!
        //根据传入的屏幕方法设置高度
        self.frame.size.height = isLaspace ? kDockLaspaceHeight : kDockLaspaceHeight * CGFloat(count)
        //y值
        self.frame.origin.y = (superview?.frame.height)! - frame.size.height
        //遍历
        for var i = 0 ; i < count ;i++ {
            //根据角标取出按钮
            let item = subviews[i]
            //设置按钮的尺寸
            item.frame.size.width = isLaspace ? frame.width/CGFloat(count) : frame.width
            item.frame.size.height = kDockLaspaceHeight
            item.frame.origin.x = isLaspace ? CGFloat(i) * item.frame.size.width : 0
            item.frame.origin.y = isLaspace ? 0 : CGFloat(i) * item.frame.size.width
        }
    }
—-> 3.2 创建按钮(该部分的代码比较多,我们写在类扩展的函数中)

代码块一 : (该部分的两个函数是一起使用的)

override init(frame: CGRect) {
        super.init(frame: frame)
        //初始化子控件
        setUpButton("tabbar_blog",type: .blog)
        setUpButton("tabbar_mood",type: .mood)
        setUpButton("tabbar_photo",type: .photo)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

代码块二 :

//给类扩充一个方法,用来创建子控件数
extension XFJBottom {
    @objc private func setUpButton(imageName : String, type : ButtomMenuType ) {
        //创建按钮
        let iconButton = UIButton()
        //绑定tag,方便点击按钮的时候可以传入对应的值
        iconButton.tag = type.rawValue
        //添加按钮
        addSubview(iconButton)
        //设置按钮图片(普通图片)
        iconButton.setImage(UIImage(named: imageName), forState: .Normal)
        //设置高亮图片
        iconButton.setBackgroundImage(UIImage(named: "tabbar_separate_selected_bg"), forState: .Highlighted)
        //监听按钮点击(这是oc的方法,一定要加上@objc)
        iconButton.addTarget(self, action: "buttonClick:", forControlEvents: .TouchUpInside)
    }
}

八 中间模块

1 和底部模块基本一样,也是通过在XFJDock类中提供的roteLspaces (isLaspace : Bool)函数,用来最为将isLaspace的屏幕状态来计算该部分情况

2 注意 : 该部分横屏和竖屏不同的是,在横屏的时候能显示图片和文字,但是在屏幕竖屏的时候,只显示图片.所以这部分需要做判断.

3 在XFJDock类中懒加载中间部分,然后添加到dock中

//懒加载中间工具栏
    private lazy var middleMenu : XFJTabBar = {
        //创建中间工具栏
        let middleMenu = XFJTabBar()
        //添加
        self.addSubview(middleMenu)
        //设置中间工具栏底部不变,顶部随着父控件拉伸而拉伸
        middleMenu.autoresizingMask = .FlexibleTopMargin
        //返回
        return middleMenu
    }()

4 根据传入的屏幕方向,计算中间模块的尺寸

//根据外界传入的屏幕方向,计算高度和宽度
    func roteTabLaspace(isLaspace : Bool) {
        //中间部分的子控件总数
        let count = subviews.count
        //宽度跟随父控件
        frame.size.width = (superview?.frame.width)!
        //高度
        frame.size.height = kDockLaspaceHeight * CGFloat(count)
        //遍历
        for var i = 0 ; i < count ; i++ {
            //取出item
            let item = subviews[i]
            //设置尺寸
            item.frame.size.width = (superview?.frame.width)!
            item.frame.size.height = kDockLaspaceHeight
            item.frame.origin.x = 0
            item.frame.origin.y = item.frame.size.height * CGFloat(i)
        }
    }

5 添加中间模块的按钮(由于该部分的代码比较多,那么我们写在类扩展的方法中)

代码块一 :
//重写initWithFrame
    override init(frame: CGRect) {
        super.init(frame: frame)

        //添加标题
        setUpTabBarItem(imageName: "tab_bar_feed_icon", title: "全部动态")
        setUpTabBarItem(imageName: "tab_bar_passive_feed_icon", title: "与我相关")
        setUpTabBarItem(imageName: "tab_bar_pic_wall_icon", title: "照片墙")
        setUpTabBarItem(imageName: "tab_bar_e_album_icon", title: "电子相框")
        setUpTabBarItem(imageName: "tab_bar_friend_icon", title: "好友")
        setUpTabBarItem(imageName: "tab_bar_e_more_icon", title: "其它")
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
代码块二 :
//MARK: - 添加中间的子标题
extension XFJTabBar {
    private func setUpTabBarItem(imageName imageName : String, title : String) {
        //创建按钮
        let button = XFJItemBtn()
        //绑定tag
        button.tag = subviews.count
        //添加按钮
        addSubview(button)
        //设置普通按钮
        button.setTitle(title, forState: .Normal)
        //设置普通图片
        button.setImage(UIImage(named: imageName), forState: .Normal)
        //设置高亮图片
        button.setBackgroundImage(UIImage(named: "tabbar_separate_selected_bg"), forState: .Selected)
        //监听按钮
        button.addTarget(self, action: "buttonClick:", forControlEvents: .TouchDown)
    }
}

6 处理中间部分按钮选中和反选中情况

—-> 6.1 创建一个属性来记录按钮的状态
//定义一个属性,用来记录按钮点击的状态
    private var selectButton = UIButton?()
—-> 6.2 处理按钮
/MARK: - 实现事件监听的方法
extension XFJTabBar {
    @objc private func buttonClick(button : UIButton) {
        //如果selectButton?.ta有值就用它的值,否则就用0
        let tag = selectButton?.tag ?? 0
        //实现代理方法
        tabBarItemDelegate?.XFJTabBarItem!(tag, to: button.tag)
        selectButton?.selected = false
        selectButton = button
        selectButton?.selected = true
    }
}

7 取消按钮的高亮状态

—-> 7.1 创建桥接文件(如果自己不想配置,就让系统体你配置)

—-> 7.2 创建OC文件

—-> 7.3 在OC文件中重写按钮的高亮方法
- (void)setHighlighted:(BOOL)highlighted{
}

8 自定义按钮

—-> 8.1 自定义原因 : 由于我们需要修改当屏幕是横屏和竖屏两种状态下,按钮的文字出现与隐藏的效果,需要用自定义来解决
—-> 8.2 创建继承XFJCustomBtn的自定义文件

—-> 8.3 重写系统的init(frame: CGRect)方法,并且做相关设置(代码里有说明)
class XFJItemBtn: XFJCustomBtn {
    //定义一个属性用来计算图片占的比例
    var  ratio : CGFloat = 0.4
    override init(frame: CGRect) {
        super.init(frame: frame)
        //仅仅让图片高亮
        adjustsImageWhenHighlighted = false
        //设置图片居中
        imageView?.contentMode = .Center
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
—-> 8.4 由于是继承按钮,所以重写按钮中的两个方法 : 一个是对按钮中标题的处理和另一个是对按钮中图片的处理(函数内部代码比较易懂,我这里就不多加说明了)

代码块一 : 按钮标题

//MARK: - 按钮标题
extension XFJItemBtn {
    override func titleRectForContentRect(contentRect: CGRect) -> CGRect {
        if frame.height == frame.width { //竖屏
            return CGRectZero
        }else{//横屏
            let width : CGFloat = frame.width * (1 - ratio)
            let height : CGFloat = frame.height
            let x : CGFloat = frame.width * ratio
            let y : CGFloat = 0
            return CGRectMake(x, y, width, height)
        }
    }
}

代码块二 : 按钮图片

//MARK: - 按钮图片
extension XFJItemBtn {
    override func imageRectForContentRect(contentRect: CGRect) -> CGRect {
        if frame.height == frame.width { //竖屏
            return bounds
        }else{//横屏
            let width : CGFloat = frame.width * ratio
            let height : CGFloat = frame.height
            let x : CGFloat = 0.0
            let y : CGFloat = 0.0
            return CGRectMake(x, y, width, height)
        }
    }
}

九 头像模块

1 在XFJDock类中对头像模块的懒加载和添加到dock中

//懒加载头像
    private lazy var iconButton : XFJIconButton = {
        //创建头像
        let iconButton = XFJIconButton()
        //添加头像
        self.addSubview(iconButton)
        //
        //返回
        return iconButton
    }()

2 通过XFJDock类中的roteLspaces (isLaspace : Bool)函数,将屏幕的状态值传入XFJIconButton类中的roteToLapace(isLapace : Bool)函数,用来计算头像的尺寸.

func roteToLapace(isLapace : Bool) {
        //icon的高度
        frame.size.height = isLapace ? kIconButtonLaspaceHeight : kIconProtraitWH
        frame.size.width = isLapace ? kIconButtonLaspaceWidth : kIconProtraitWH
        frame.origin.y = kIconLpaceY
        frame.origin.x = ((superview?.frame.width)! - frame.width) * 0.5
        self.isLapace = isLapace
    }

3 添加头像中的文字和图片设置

//重写init方法
    override  init(frame: CGRect) {
        super.init(frame: frame)
        //设置头像图片
        setImage(UIImage(named: "icon"), forState: .Normal)
        //设置文字
        setTitle("不良少年J", forState: .Normal)
        //设置文字显示位置
        titleLabel?.textAlignment = .Center
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

4 由于头像的本质是一个按钮,那么我们可以通过重写按钮中对图片和对标题的方法来对图片进行设置

头部按钮的图片设置代码 : (易懂)

//MARK: - 头部按钮的图片
extension XFJIconButton {
    override func imageRectForContentRect(contentRect: CGRect) -> CGRect {
        if frame.width == frame.height { //竖屏
            return bounds
        }else{//横屏
            let width : CGFloat = kIconButtonLaspaceWidth
            let height : CGFloat = kIconButtonLaspaceHeight
            let x : CGFloat = 0.0
            let y : CGFloat = 0.0
            return CGRectMake(x, y, width, height)
        }
    }
}

头部按钮的标题设置代码 : (易懂)

//MARK: - 按钮的标题
extension XFJIconButton {
    override func titleRectForContentRect(contentRect: CGRect) -> CGRect {
        if frame.width == frame.height {//竖屏
            return CGRectZero
        }else{//横屏
            let width : CGFloat = kIconButtonLaspaceWidth
            let height : CGFloat = kIconTextHeight
            let x : CGFloat = 0.0
            let y : CGFloat = kIconButtonLaspaceHeight
            return CGRectMake(x, y, width, height)
        }
    }
}

十 底部模块业务逻辑

1 需求 : 当点击底部按钮的时候,弹出控制器,并且点击弹出的控制器中的按钮,控制器消失.

2 采用方法 : 代理;协议

3 谁可以成为代理? 主控制器(XFJHomeViewController).原因是因为当点击底部按钮时,是有主控制器弹出子控制器.

4 弹出方式 ? model

5 设置枚举(外面通过switch的判断方式来选中model出对应的控制器)

//MARK : - 定义枚举
@objc enum ButtomMenuType : Int {
    case blog
    case mood
    case photo
}

6 定义协议方法

//MARK : - 定义协议
@objc protocol XFJBottomDelegate : NSObjectProtocol {
    //可实现的
    optional func XFJBottomMenuClickItem(menu : XFJBottom, type : ButtomMenuType)
}

7 定义代理

//设置代理属性
    weak var BottomMenuDelegate :XFJBottomDelegate?

8 监听底部模块按钮的点击(这是oc的方法,一定要加上@objc)

//监听按钮点击(这是oc的方法,一定要加上@objc)
        iconButton.addTarget(self, action: "buttonClick:", forControlEvents: .TouchUpInside)

8 处理按钮点击事件

//MARK: - 点击底部按钮,将相应的值传入协议方法中
extension XFJBottom {
    @objc private func buttonClick(button : UIButton) {
        BottomMenuDelegate?.XFJBottomMenuClickItem!(self, type: ButtomMenuType(rawValue: button.tag)!)
    }
}

9 设置代理(在XFJHomeViewController的类中)

//设置底部工具条的代理
        dock.getBottomMenu.BottomMenuDelegate = self

10 实现协议方法

//MARK : - 实现底部协议的方法
extension XFJHomeViewController {
    func XFJBottomMenuClickItem(menu: XFJBottom, type: ButtomMenuType) {
        switch type {
        case .blog :
            print("点击了blog")
        case .mood :
            //创建控制器
            let moodVC = XFJMoodViewController()
            //将该控制器作为导航控制器的根控制器
            let nav = UINavigationController(rootViewController: moodVC)
            //设置控制器样式
            nav.modalPresentationStyle = .FormSheet
            //model
            presentViewController(nav, animated: true, completion: nil)
            print("点击了mood")
        case .photo :
            print("点击了photo")
        }
    }
}

十一 中间模块的业务逻辑

1 由于用户点击中间模块的按钮的时候,是对子控制器的切换,那么我们需要创建所有的子控制器(在XFJHomeViewController类中书写;考虑代码比较多,写在类扩展中)

//MARK : - 添加子控制器
extension XFJHomeViewController {
    private func setUpAllChildController() {
        //全部动态
        let allDynamicStateController = XFJAllSegViewController()
        allDynamicStateController.view.backgroundColor = UIColor.blueColor()
        setUpNavController(allDynamicStateController)
        //与我相关
        let andMeCorrelationController = UIViewController()
        andMeCorrelationController.view.backgroundColor = UIColor.redColor()
        andMeCorrelationController.title = "与我相关"
        setUpNavController(andMeCorrelationController)
        //照片墙
        let photoWallController = UIViewController()
        photoWallController.view.backgroundColor = UIColor.yellowColor()
        photoWallController.title = "照片墙"
        setUpNavController(photoWallController)
        //电子相框
        let electronRahmenController = UIViewController()
        electronRahmenController.view.backgroundColor = UIColor.purpleColor()
        electronRahmenController.title = "电子相框"
        setUpNavController(electronRahmenController)
        //好友
        let friendsController = UIViewController()
        friendsController.view.backgroundColor = UIColor.orangeColor()
        friendsController.title = "好友"
        setUpNavController(friendsController)
        //其它
        let otherController = UIViewController()
        otherController.view.backgroundColor = UIColor.whiteColor()
        otherController.title = "其它"
        setUpNavController(otherController)
        //添加一个头像子控制器
        let iconController = UIViewController()
        iconController.view.backgroundColor = UIColor.brownColor()
        iconController.title = "个人中心"
        setUpNavController(iconController)
    }
}

2 由于所有的子控制器都是作为导航控制器的根控制器,所以定义一个方法来设置导航控制器

//MARK : - 定义一个方法用来设置导航控制器
extension XFJHomeViewController {
    private func setUpNavController(root : UIViewController){
        let nav1 = UINavigationController(rootViewController: root)
        addChildViewController(nav1)
    }
}

3 设置中间子控制器的view

//MARK : - 设置内容的view
extension XFJHomeViewController {
    private func setUpContentView() {
        //判断是否子控制器存在,如果存在就直接返回
        guard childViewControllers != [] else{
            return
        }
        //创建一个属性
        let contentive = UIView()
        //添加contentView
        view.addSubview(contentive)
        //设置子控制器的view尺寸
        contentive.frame.origin.x = dock.frame.width
        contentive.frame.origin.y = 20.0
        contentive.frame.size.height = view.frame.height - 20.0
        //高度跟随父控件拉伸而拉伸
        contentive.autoresizingMask = .FlexibleHeight
        //用宽度和高度取出里面最小的
        let ProtraitWidth = min(view.frame.height, view.frame.width)
        //计算子控制器的宽度
        contentive.frame.size.width = ProtraitWidth - kDockProtraitWidth
        //赋值(用于屏幕旋转的时候更新x值)
        self.contentive = contentive
    }
}

4 定义协议

//MARK: - 协议方法
@objc
protocol XFJTabBarDelegate : NSObjectProtocol {
    //这里用两个参数的原因是由于选中状态和没有选中状态需要切换,那么需要将不同的tag传入其中,才能令对应的控制器切换
    optional func XFJTabBarItem(from : Int, to : Int)
}

5 设置代理

//定义代理
    weak var tabBarItemDelegate : XFJTabBarDelegate?

6 实现代理方法(该部分代码是在监听按钮点击事件中个书写的)

//实现代理方法
        tabBarItemDelegate?.XFJTabBarItem!(tag, to: button.tag)

7 设置代理

//设置中间部分工具条的代理
        dock.getTabBarItem.tabBarItemDelegate = self

8 实现协议方法

//MARK: - 实现中间部分协议方法
extension XFJHomeViewController : XFJTabBarDelegate{
    func XFJTabBarItem(from: Int, to: Int) {
        //根据传入的参数,取出上移个view
        let previousController = childViewControllers[from]
        //移除上一个控制器的view
        previousController.view.removeFromSuperview()
        //取出下一个控制器的view
        let nextVC = childViewControllers[to]
        //将切换的子控制器的view添加到contentView中
        contentive?.addSubview(nextVC.view)
        //计算nextVC的尺寸
        nextVC.view.frame = (contentive?.bounds)!
        //下一个按钮的角标
        selectButtonIndex = to
    }
}

十二 头像模块业务逻辑处理

1 在XFJHomeViewController类中实现对头像按钮的监听

2 监听方法(需要加上@objc)

//监听头像点击
        dock.getIcon.addTarget(self, action: "iconButtonClick", forControlEvents: .TouchUpInside)

3 实现头像监听方法

//MARK : - 实现头像监听方法
extension XFJHomeViewController {
    @objc private func iconButtonClick() {
        //调用点击中间部分的内容
        XFJTabBarItem(selectButtonIndex, to: childViewControllers.count - 1)
        //调用取消选中状态
        dock.getTabBarItem.unSelecButton()
    }
}

十三 各个模块中的get方法

1 我们是在XFJHomeViewController类中设置其为各个模块的代理,但是我们在该类中是无法直接拿到对应的模块的代理,那么我们是通过什么办法的呢?—–> get方法

2 在各个模块创建的时候,我们直接提供一个get方法,这样很大的方便了外界可以间接的拿到模块对象

//对底部工具栏提供只读方法
    var getBottomMenu : XFJBottom {
        get{
            return bottomMenu
        }
    }
//对中间工具条提供只读的方法
    var getTabBarItem : XFJTabBar {
        get{
            return middleMenu
        }
    }
//给头像按钮提供一个只读的属性,让外界可以拿到iconButton按钮
    var getIcon : XFJIconButton {
        get{
            return iconButton
        }
    }

3 通过get方式拿到模块设置代理和对头像的监听

//设置底部工具条的代理
        dock.getBottomMenu.BottomMenuDelegate = self
        //设置中间部分工具条的代理
        dock.getTabBarItem.tabBarItemDelegate = self
        //监听头像点击
        dock.getIcon.addTarget(self, action: "iconButtonClick", forControlEvents: .TouchUpInside)

十四 细节

1 默认程序启动进入主界面,显示的是头像模块

2 在XFJTabBar提供一个方法取消选中状态

//提供一个方法取消选中状态(由外界调用)
    func unSelecButton() {
        selectButton?.selected = false
    }

3 设置程序已启动进入主界面当前的角标为0

//设置一个属性当前按钮角标
    private var selectButtonIndex : Int = 0

4 将该属性传入对头像按钮监听的方法中,就能实现默认选中头像模块了

//MARK : - 实现头像监听方法
extension XFJHomeViewController {
    @objc private func iconButtonClick() {
        //调用点击中间部分的内容
        XFJTabBarItem(selectButtonIndex, to: childViewControllers.count - 1)
        //调用取消选中状态
        dock.getTabBarItem.unSelecButton()
    }
}

十五 补充

1 中间模块中的导航条的标题进行处理UISegmentedControl

2 该部分没什么可以说的,比较简单,下面是代码

class XFJAllSegViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        //设置背景颜色
        view.backgroundColor = UIColor.whiteColor()
        //创建seg
        let seg = UISegmentedControl(items: ["全部","特别关心","好友动态","认证空间"])
        //添加
        navigationItem.titleView = seg
        //设置颜色
        seg.tintColor = UIColor.grayColor()
        //设置默认选中第0个
        seg.selectedSegmentIndex = 0
        //设置选中文字
        seg.setTitleTextAttributes([NSForegroundColorAttributeName : UIColor.blackColor()], forState: .Normal)
        //监听点击事件
        seg.addTarget(self, action: "segClick", forControlEvents: .TouchUpInside)
    }
}

3 点击底部模块,model出控制器,对model出来的控制器相关设置

class XFJMoodViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        //设置标题
        self.title = "发表说说"
        //设置颜色
        view.backgroundColor = UIColor.redColor()
        //设置导航条左边的内容
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "关闭", style: .Plain, target: self, action: "exit")
        //设置导航条右边的内容
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "发表", style: .Plain, target: self, action: "deliver")
    }
}

4 dismiss

//Mark : - dismiss控制器
extension XFJMoodViewController {
    @objc private func exit() {
        dismissViewControllerAnimated(true, completion: nil)
    }
}

十六 总结

1 总结的差不多了,只要我考虑到的情况,我都写在了里面,希望对个位能有所帮助吧.里面依旧有不完善的地方.后续想到了会完善.

2 逻辑适中,要求能理解协议的用法.明白怎么对屏幕横屏和竖屏的判断,通过横屏和竖屏对每个模块尺寸的设置.

3 最后大家如果觉得我写得还行,麻烦大家关注我的官方博客,有什么意见大家给我留言即可,谢谢!!!!

时间: 2024-10-12 20:04:00

ipad版Qzone(纯swift编写)的相关文章

ipad版简单美团界面功能实现(纯swift编写)

一 总体功能图一 : (ipad竖屏) 二 总体功能图二 : (ipad横屏) 三 讲解内容 1 搭建美团界面(掌握) 2 ios8.0之后的Popover的运用(重点) 3 协议(掌握) 4 通知(掌握) 5 细节处理 四 总体界面 1 由总体的app界面效果,能看出来,一个UIViewController控制器作为UINavigationController的根控制器就能满足条件. 五 导航条设置 1 自定义导航条 : (系统的导航条不能满足需求) 2 创建导航控制器类 3 获取全局的导航条

Swift编写的一些完整的app

收集了一些实用swift编写的app,这些demo都是不错的值得学习的. 知乎日报  Swift-ZhihuDaily Swift版知乎日报 参照了YANGReal的糗事百科和uitableview的例子,这些虽说是demo,也是完整的app,使用了爱加密的app加密技术,非常不错,感谢! 我觉得学习一门语言光看是没用的,只有投入到实践项目中去,才能最快速的掌握它,有兴趣的同学可以和我一起来把这个项目完善,目前只做了列表页和内容页两个页面. 源码地址:Swift-ZhihuDaily 新浪新闻客

[转]IFTTT开源Swift编写的帧动画框架--RazzleDazzle

RazzleDazzle 是IFTTT开源的一个iOS帧动画框架,用Swift编写,非常适用于APP初次使用时的介绍和引导信息.RazzleDazzle由IFTTT此前开源的一款Objective-C滚动帧动画库JazzHands发展而来.JazzHands是UIKit一个简单的关键帧基础动画框架,可通过手势.scrollview.KVO或者ReactiveCocoa控制动画,被IFTTT应用在IFTTT for iPhone上.多款知名应用程序都使用了JazzHands这个框架,目前其在git

---纯Swift的中SQLite 的使用

SQLite.swift 是一个使用纯 Swift 语言封装 SQLite3 的操作框架. 特性: 简单的查询和参数绑定接口 安全.自动类型数据访问 隐式提交和回滚接口 开发者友好的错误处理和调试 文档完善 通过广泛测试 示范代码: import Foundation /** 1. 打开数据库 2. 如果没有数据表,需要首先创表 3. 数据操作 */ class SQLite { var db: COpaquePointer = nil /// 打开数据库 /// /// :param: dbn

RadioStream应用源码完整版(iphone版和ipad版)

RadioStream应用源码完整版(iphone版和ipad版),这个项目是从那个ios教程网分析过了的,也是一个不错的国外音乐应用的,支持iphone版和ipad版. <ignore_js_op> <ignore_js_op> <ignore_js_op><ignore_js_op> <ignore_js_op> 详细说明:http://ios.662p.com/thread-178-1-1.html RadioStream应用源码完整版(i

iPad版微信终于来临了 微信5.4版搜索更智能 转账就是发消息

等待甚久的iPad版微信终于来临了!昨日微信iOS版本更新至5.4.0.16,新增功能包括搜索公众号.识别图中二维码.面对面收钱,同时适配iPad. 1.先来揭开iPad版微信应用的面纱 微信已有iPhone和Mac版应用,但并没有独立的iPad版应用.此次更新后,微信推出了适配iPad的版本.用户除了输入手机号码登录iPad微信外,还可通过扫描二维码的方式来登录. iPad登录方式与网页版有相同之处,用户打开iPad版应用后,需用手机扫描iPad上出现的二维码,点击“登录iPad微信”确认,但

纯 Swift 封装的 SQLite 框架:SQLite.swift

SQLite.swift 是一个使用纯 Swift 语言封装 SQLite3 的操作框架. 特性: 简单的查询和参数绑定接口 安全.自动类型数据访问 隐式提交和回滚接口 开发者友好的错误处理和调试 文档完善 通过广泛测试 示例代码: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import SQLite   let db = Database("

纯CSS3编写的面包屑导航收集

整理了10个纯CSS3制作的面包屑导航,这些都是通过CSS3来编写,十分方便,而且实用.有些文章附有教程,大家可以研究学习一下. 漂亮面包屑导航 查看网站 扁平化面包屑导航 查看网站 圆形风格面包屑导航 查看网站 CSS面包屑导航 查看网站 CSS3面包屑导航制作教程 查看网站 黄色的CSS Breadcrumbs教程 查看网站 CSS3 Breadcrumbs 查看网站 扁平化风格面包屑制作教程 查看网站 CSS 面包屑菜单制作方法 查看网站 五个不同风格的面包屑导航CSS制作教程 查看网站

5个步骤,将 storyboard 从 iphone 版转变为 ipad 版

1.将 iPhone 版的 Storyboard 复制为 iPad 的,比如 Main_iPad.storyboard 2.用文本编辑器(不要用 Xcode)打开 Main_iPad.storyboar. 3.替换全部的 targetRuntime="iOS.CocoaTouch" 为 targetRuntime="iOS.CocoaTouch.iPad". 4.替换全部的 <simulatedScreenMetrics key="destinati