基础控制器的构建与设备方向的控制

文章来源:爱听音乐的狗博客

涉及知识点

1、基于UIViewController构建基础控制器
2、基于UINavigationController构建导航控制器并添加Pop手势
3、基于UITabBarController构建主控制器
4、指定控制器可进行设备方向翻转

具体实现

1、基于UIViewController构建基础控制器

1.1 自定义导航栏

在定制导航栏中所包含的内容时,主要是考虑绝大部分控制器所需要的控件,所以通常包含左返回按钮,标题视图以及标题,右侧设置按钮。部分iPhone手机带有刘海,statusBar的高度也不再是以前的20,所以更好做法是将导航视图分为statusBar高度的视图和其他视图,一起构成导航视图。如下所示

    // MARK: set & get 方法
    
    
    /// navigationView
    lazy var navigationView: UIView = {
        let navigationView = UIView.init(frame: CGRect.zero)
        navigationView.backgroundColor = UIColor.white
        navigationView.addSubview(self.statusView)
        navigationView.addSubview(self.moreNaviView)
        navigationView.addSubview(self.lineView)
        return navigationView
    }()
    /// 去除状态栏剩余view
    lazy var moreNaviView: UIView = {
        let moreNaviView = UIView.init(frame: CGRect.zero)
        view.backgroundColor = UIColor.white
        moreNaviView.addSubview(self.titleView)
        return moreNaviView
    }()
    /// 状态栏高度的视图
    lazy var statusView: UIView = {
        let statusView = UIView.init(frame: CGRect.zero)
        statusView.backgroundColor = UIColor.white
        return statusView
    }()
    
    lazy var backButton: UIButton = {
        let button = UIButton.init(frame: CGRect.init(x: 10, y: 0, width: 100, height: 44))
        button.setTitle("返回", for: .normal)
        button.setTitleColor(UIColor.black, for: .normal)
        button.setTitleColor(UIColor.lightGray, for: .highlighted)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 16)
        button.setImage(UIImage.init(named: "黑色向左箭头"), for: .normal)
        button.setImage(UIImage.init(named: "向左箭头"), for: .highlighted);
        button.titleLabel?.adjustsFontSizeToFitWidth = true
        button.contentHorizontalAlignment = .left
        button.titleEdgeInsets = UIEdgeInsets.init(top: 0, left: 5, bottom: 0, right: 0)
        button.addTarget(self, action: #selector(backButtonDidClicked(button:)), for: .touchUpInside)
        return button
    }()
    
    lazy var rightButton: UIButton = {
        let button = UIButton.init(type: UIButton.ButtonType.custom)
        button.setTitleColor(UIColor.black, for: UIControl.State.normal)
        button.setTitleColor(UIColor.lightGray, for: .highlighted)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 16)
        button.titleLabel?.adjustsFontSizeToFitWidth = true
        button.titleLabel?.contentMode = UIView.ContentMode.center
        button.addTarget(self, action: #selector(rightButtonDidClicked(button:)), for: .touchUpInside)
        return button
    }()
    
    lazy var lineView: UIView = {
        let lineView = UIView.init(frame: CGRect.zero)
        lineView.backgroundColor = UIColor.gray
        return lineView
    }()
    
    // MARK: 私有 set & get 方法
    
    private lazy var titleView: UIView = {
        let titleView = UIView.init(frame: CGRect.zero)
        titleView.addSubview(self.titleLabel)
        return titleView
    }()
    
    private lazy var titleLabel :UILabel =  {
        let label = UILabel.init(frame: CGRect.zero)
        label.font = UIFont.systemFont(ofSize: 16)
        label.adjustsFontSizeToFitWidth = true
        label.textAlignment = .center
        return label
    }()

1.2 添加导航栏视图并布局

可在控制器中重写viewDidLoad()方法,进行视图的添加,需要注意的是自定义导航栏视图部分控件的frame不能写死,需要实现自动布局,否则在某些指定的控制器中实现设备方向翻转时导航视图会出现frame问题,使用的布局框架是SnapKit,布局如下所示

    //MARK: 重写的方法和属性
    override func viewDidLoad() {
        super.viewDidLoad()
        // 添加基本视图
        self.configBaseUI()
        
    }
    
    // MARK: 本类公共方法
    /// 设置UI
    private func configBaseUI() {
        self.view.backgroundColor = UIColor.colorWithHex(rgb: 0xb2b2b2)
        self.view.addSubview((self.navigationView))
        self.view.bringSubviewToFront(self.navigationView)
        self.makeConstraint()
    }
    
    /// 约束
    private func makeConstraint() {
        self.navigationView.snp.makeConstraints { (make) in
            make.left.equalToSuperview()
            make.right.equalToSuperview()
            make.top.equalToSuperview()
            make.height.equalTo(JJ_NAVIGATION_HEIGHT)
        }
        self.statusView.snp.makeConstraints { (make) in
            make.left.equalToSuperview()
            make.right.equalToSuperview()
            make.top.equalToSuperview()
            make.height.equalTo(JJ_STATUS_HEIGHT)
        }
        self.moreNaviView.snp.makeConstraints { (make) in
            make.left.equalToSuperview()
            make.right.equalToSuperview()
            make.top.equalTo(JJ_STATUS_HEIGHT)
            make.height.equalTo(JJ_NAVIGATION_MOREHEIGHT)
        }
        self.lineView.snp.makeConstraints { (make) in
            make.left.equalToSuperview()
            make.right.equalToSuperview()
            make.top.equalTo(JJ_NAVIGATION_HEIGHT - 1)
            make.height.equalTo(1)
        }
        self.titleView.snp.makeConstraints { (make) in
            make.left.equalToSuperview().offset(100)
            make.right.equalToSuperview().offset(-100)
            make.top.equalToSuperview()
            make.height.equalToSuperview()
        }
        self.titleLabel.snp.makeConstraints { (make) in
            make.left.equalToSuperview()
            make.right.equalToSuperview()
            make.top.equalToSuperview()
            make.height.equalToSuperview()
        }
    }

在做布局时使用到了JJ_NAVIGATION_HEIGHT、JJ_STATUS_HEIGHT、JJ_NAVIGATION_MOREHEIGHT等常量,主要作用是需要做屏幕适配(关于适配方面的问题,可以参考iPhone X 屏幕适配从选择到 "放弃"),涉及到的3个常量如下所示:

   /// iphone x/xs/xsMax/xr 44, 其他20    iphonex/xs: 375.f, 812.f iphonexr/xsMax: 414.f, 896.f
   let JJ_STATUS_HEIGHT = ((__CGSizeEqualToSize(CGSize.init(width: 375, height: 812), UIScreen.main.bounds.size) == true ||
                         __CGSizeEqualToSize(CGSize.init(width: 812, height: 375), 
   UIScreen.main.bounds.size) == true ||
                         __CGSizeEqualToSize(CGSize.init(width: 414, height: 896), 
   UIScreen.main.bounds.size) == true ||
                         __CGSizeEqualToSize(CGSize.init(width: 896, height: 414), 
   UIScreen.main.bounds.size) == true)
                        ? 44 : 20)

   /// navigation除掉status的高度
   let JJ_NAVIGATION_MOREHEIGHT = (44)

   /// navigation高度
   let JJ_NAVIGATION_HEIGHT = (JJ_STATUS_HEIGHT + 
   JJ_NAVIGATION_MOREHEIGHT)

1.3 状态栏的控制

有时需要对状态栏显示的状态以及是否隐藏状态栏进行控制,但是对应的"preferredStatusBarStyle"与"prefersStatusBarHidden"属性只提供了get,而不能进行set,所以需要重写对应的属性,实现对状态栏的控制

    
    // MARK: 属性
    
    /// 状态栏状态
    private var _statusBarStyle:UIStatusBarStyle?
    /// 状态栏状态
    var statusBarStyle: UIStatusBarStyle? {
        get {
            return _statusBarStyle
        }
        set {
            _statusBarStyle = newValue
            self.setNeedsStatusBarAppearanceUpdate()
        }
    }
    /// 是否隐藏状态栏
    private var _statusBarHidden:Bool?
    /// 是否隐藏状态栏
    var statusBarHidden: Bool? {
        get {
            return _statusBarHidden
        }
        set {
            _statusBarHidden = newValue;
            self.setNeedsStatusBarAppearanceUpdate()
        }
    }
    
    //MARK: 重写的方法和属性
    override var preferredStatusBarStyle: UIStatusBarStyle {
        if self.statusBarStyle != nil {
            return self.statusBarStyle!
        }
        return super.preferredStatusBarStyle
    }
    
    override var prefersStatusBarHidden: Bool {
        if self.statusBarHidden != nil {
            return self.statusBarHidden!
        }
        return super.prefersStatusBarHidden
    }

1.4 方法调用

在基础控制器中应当提供常用的方法,用于方便子控制器来进行调用,提高效率,比如说导航栏的基本设置、颜色、文字等,需要注意的是按钮点击的方法需要加上 @objc才能公开给Objective-C,如下所示

    
    // MARK: 子类调用方法
    
    
    /// 导航栏基本设置
    func defaultNavigation() {
        self.setNavigation(color: UIColor.white)
        self.statusBarStyle = .default
        self.setNavigation(titleColor: UIColor.black)
    }
    
    /// 设置导航栏背景色
    func setNavigation(color:UIColor) {
        self.navigationView.backgroundColor = color
        self.moreNaviView.backgroundColor = color
        self.statusView.backgroundColor = color
    }
    /// 设置导航栏文字
    func setNavigation(title: String) {
        self.setNavigation(title: title, titleColor: nil)
    }
    
    /// 设置导航栏文字颜色
    ///
    /// - Parameter titleColor: titleColor description
    func setNavigation(titleColor:UIColor) {
        self.setNavigation(title: nil, titleColor: titleColor)
    }
    /// 设置导航栏文字和文字颜色
    func setNavigation(title: String?, titleColor:UIColor?) {
        self.titleLabel.text = title ?? ""
        self.titleLabel.textColor = titleColor ?? UIColor.black
    }
    /// 设置导航栏文字和导航栏颜色
    func setNavigation(color:UIColor, title:String) {
        self.setNavigation(color: color)
        self .setNavigation(title: title)
    }
    /// 添加返回按钮
    func addBackButton() {
        self.moreNaviView.addSubview(self.backButton)
    }
    
    /// 添加右侧按钮
    ///
    /// - Parameters:
    ///   - title: title description
    ///   - titleColor: titleColor description
    ///   - imageName: imageName description
    func addRightButton(title:String?, titleColor:UIColor?, imageName:String? ) {
        self.rightButton.setTitle(title, for: UIControl.State.normal)
        self.rightButton.setTitleColor(titleColor, for: UIControl.State.normal)
        self.rightButton.setImage(UIImage.init(named: imageName ?? ""), for: UIControl.State.normal)
        self.moreNaviView.addSubview(self.rightButton)
        self.rightButton.snp.makeConstraints { (make) in
            make.width.equalTo(JJ_NAVIGATION_MOREHEIGHT)
            make.height.equalTo(JJ_NAVIGATION_MOREHEIGHT)
            make.top.equalToSuperview()
            make.right.equalToSuperview().offset(-10)
        }
    }
    
    /// 添加右侧按钮
    ///
    /// - Parameter imageName: imageName description
    func addRightButton(imageName:String) {
        self.addRightButton(title: nil, titleColor: nil, imageName: imageName)
    }
    
    /// 添加右侧按钮
    ///
    /// - Parameters:
    ///   - title: title description
    ///   - titleColor: titleColor description
    func addRightButton(title:String, titleColor:UIColor) {
        self.addRightButton(title: title, titleColor: titleColor, imageName: nil)
    }
    
    /// 是否显示导航栏底部灰线
    ///
    /// - Parameter show: show description
    func showNavBottomLine(show:Bool) {
        self.lineView.isHidden = !show
    }
    
    /// 是否显示导航栏
    ///
    /// - Parameter bool: bool
    func showNavigationBar(bool: Bool) {
        self.navigationView.isHidden = !bool
    }
    
    /// 返回按钮被点击,默认pop,需要时子类可重写 @objc修饰的方法子类也可重写
    @objc func backButtonDidClicked(button:UIButton) {
        self.navigationController?.popViewController(animated: true)
    }
    
    /// 右侧按钮被点击 需要时子类可重写 @objc修饰的方法子类也可重写
    ///
    /// - Parameter button: button description
    @objc func rightButtonDidClicked(button:UIButton) {
        
    }

2、基于UINavigationController构建导航控制器并添加Pop手势

创建控制器基于UINavigationController,准守UINavigationControllerDelegate与 UIGestureRecognizerDelegate协议,实现协议下对应的方法,在对应方法中对interactivePopGestureRecognizer的isEnabled进行控制以及是否响应手势,如下所示

 override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
        if self.responds(to: #selector(getter: interactivePopGestureRecognizer)) {
            self.interactivePopGestureRecognizer?.delegate = self
        }
    }
 
    //MARK: UINavigationControllerDelegate
    func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
        // 在最底层的viewController是不能够响应pop手势的
        if self.responds(to: #selector(getter: interactivePopGestureRecognizer)) {
            if self.viewControllers.count == 1 {
                self.interactivePopGestureRecognizer?.isEnabled = false
            } else {
                self.interactivePopGestureRecognizer?.isEnabled = true
            }
        }
    }
    //MARK: UIGestureRecognizerDelegate
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        // 在最底层的viewController是不应该开始手势
        if self.viewControllers.count < 2 || self.visibleViewController == self.viewControllers[0] {
            return false
        }
        return true
    }

3、基于UITabBarController构建主控制器

主要是实现对应的几个属性,对控制器进行基于JJNavigationViewController的包装,然后添加为子控制器,如下所示


    override func viewDidLoad() {
        super.viewDidLoad()
        self.configUI()
    }
    // MARK: 私有方法
    
    private func configUI() {
        //基于JJBaseViewController基础控制器构建4个控制器
        self.addChildViewController(viewController: JJHomeMainViewController.init(), title: "首页", image: "信息前", selectImage: "信息后")
        self.addChildViewController(viewController: JJFindViewController.init(), title: "发现", image: "发现前", selectImage: "发现后")
        self.addChildViewController(viewController: JJVideoViewController.init(), title: "美图", image: "信息前", selectImage: "信息后")
        self.addChildViewController(viewController: JJMeViewController.init(), title: "我的", image: "发现前", selectImage: "发现后")
    }
    
    /// 添加自控制器
    ///
    /// - Parameters:
    ///   - viewController: viewController description
    ///   - title: title description
    ///   - image: image description
    ///   - selectImage: selectImage description
    private func addChildViewController(viewController:UIViewController, title:NSString, image:NSString, selectImage:NSString){
        // 设置文字和图片
        viewController.tabBarItem.title = title as String
        viewController.tabBarItem.image = UIImage.init(named: image as String)
        viewController.tabBarItem.selectedImage = UIImage.init(named: selectImage as String)
        // 设置文字个状态下的属性
        let tabBar = viewController.tabBarItem!
        tabBar.setTitleTextAttributes([NSAttributedString.Key.foregroundColor:UIColor.gray], for: .normal)
        tabBar.setTitleTextAttributes([NSAttributedString.Key.foregroundColor:UIColor.colorWithHex(rgb: 0x16a5af, alpha: 1)], for: .selected)
        
        // 包装一个当行控制器
        let nav = JJNavigationViewController.init(rootViewController: viewController)
        nav.setNavigationBarHidden(true, animated: false)
        self.addChild(nav)
    }

4、指定控制器可进行设备方向翻转

4.1 修改General配置

基础控制器的构建与设备方向的控制_第1张图片
修改配置.png

4.2 AppDelegate中添加属性、实现方法

添加sAllowRotation属性用于标记," func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask
"方法中判断sAllowRotation的值,来改变UIInterfaceOrientationMask的值。默认情况下屏幕是不会自动旋转的

    /// 是否允许屏幕旋转
    var isAllowRotation:Bool = false

    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        if self.isAllowRotation == true {
            return UIInterfaceOrientationMask.allButUpsideDown
        } else {
            return UIInterfaceOrientationMask.portrait
        }
    }

4.3 指定控制器实现屏幕的旋转

分别在对应控制器的viewDidAppear和viewDidDisappear方法中对AppDelegate的isAllowRotation属性进行修改。需要注意的是调用viewDidDisappear时,并且当前视图不是portrait,需要将设备的"orientation"属性修改为portrait,以便可旋转控制器不显示进入其他控制器时设备自动旋转为portrait

    override func viewDidAppear(_ animated: Bool) {
        self.allowRotation()
    }
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        self.notAllowRotation()
    }
    /// 设配可翻转
    private func allowRotation() {
        //设备是否可翻转
        let delegate = UIApplication.shared.delegate as? AppDelegate
        delegate?.isAllowRotation = true
    }
    
    /// 设备不可翻转
    private func notAllowRotation() {
        //如果屏幕不是portrait就修改为portrait
        if UIDevice.current.orientation != .portrait {
            let tempNumber:NSNumber = NSNumber.init(value: Int8(UIDeviceOrientation.portrait.rawValue))
            UIDevice.current.setValue(tempNumber, forKey: "orientation")
        }
        let delegate = UIApplication.shared.delegate as? AppDelegate
        delegate?.isAllowRotation = false
    }

项目地址:知识集结点

你可能感兴趣的:(基础控制器的构建与设备方向的控制)