一:访客界面效果如图
二:xib封装访客视图的view
1:业务逻辑分析:1:由于用户未登录时要显示访客视图,要先进行判断用户是否登录,未登录则显示访客视图,登录则显示正常的登陆界面,由于要在四个子控制器界面的控制器中都要判断是否显示访客视图,同样的逻辑,所以考虑抽成父类,把判断是否显示访客视图的逻辑封装在父类中,让子类去继承。2:访客视图的界面如图:将访客视图封装在一个view中,view的界面相对固定,所以用xib搭建,首先将尽可能显示的控件全部封装在view的内部,再根据外界传入的model或是在封装类中定义方法来控制控件显示的内容或是相应控件的显示和隐藏,3:通过在父类控制器中重写loadView方法判断用户是否登陆来设置是否加载访客视图
2:访客视图的搭建:新建文件继承UIView,新建xib与新建的UIview类同名,首先来到xib中,将类与xib进行关联,可以不设置freeform
1:1:先拖入UIimageView来设置旋转图片:先在如图处设置xib约束,设置控件相对于父视图水平居中竖直居中,在如图所示位置水平竖直居中(此处1设置的是相对于父视图的水平竖直居中,图2水平竖直居中对齐,一般都是任意选中两个控件,设置控件A相对于控件B的中心点水平竖直居中)。
2:先设置旋转图片相对于父视图水平竖直居中,在上图1中去设置,再来到右侧调整其竖直中心点向上偏移,按住command + =调整旋转图片的UIimageView的大小等于图片的大小,就不用再去设置宽高的约束了。
3:再拖入一个UIImageView背景图片来盖住旋转图片的底部,在如图所示的位置设置背景图片的顶部左右和高约束,(图中小箭头的部分可以设置相对于哪个控件的约束)
4:再设置房子的约束,拖入一个UIimageView,command + = 来设置imageView的大小和图片的大小相等,所以就不用设置宽高约束了,再来到如图所示的位置处设置,同时选中房子和旋转图片,拉线,按住shift键可同时设置多个约束,设置房子相对于旋转view的中心点水平居中,竖直居中(在右侧又可以调整位置的相对偏移)
5:提示lable的约束设置:拖入lable,换行,设置numberoflines为0,设置文字的居中效果,Aliment对齐方式为居中,就会显示出如图所示的效果:在左侧同时选中lable和房子,设置lable相对于房子中心点水平居中对齐,y方向的约束就设置lable距离房子底部的约束,拉线设置竖直距离。给lable设置一个最大宽度的约束,为了适配登陆注册按钮,高度约束不去设置
10:登陆注册按钮的设置:拖入注册按钮,调整样式为custom,否则不能显示出按钮的高亮状态,设置按钮的背景图片,此时背景图片会拉伸,可以在如图处设置,防止注册按钮的背景拉伸:在如图出sliciing处设置水平竖直为保护区域,不产生拉伸
宽高约束不设置,让按钮根据内容大小来自定义宽高,在设置按钮相对lable左对齐,y方向约束,设置lable相对于房子(固定不变)的底部约束,为了避免因提示文字内容的多少,而导致四个界面的登陆注册按钮不再同一个y值方向上。再拷贝粘贴出登陆按钮,拷贝粘贴的控件保留了宽高约束,所以还需要设置其右对齐和y方向上的约束。设置两个控件的对齐方式如图:1:同时选中两个控件,来到如图处位置设置两个控件的对齐方式
三:控制器代码
1:封装访客view的代码
import UIKit /* 总结:1:设置好xib后,拉线到相应的view中成为RHVisitorView的属性(对于注释:一般代码的模块化用 //MARK:-1来注释,非模块化只是可用//来表示),@IBOutlet weak var loginBtn: UIButton!,用weak修饰因为xib控件已经在该view上了,所以该view对该控件已经有了一个强引用,在swift中一般变量基本都为可选类型,要是确定其一定有值,则可以用!来进行强制解包,as?可以将左边的类型转换为右边的可选类型,as!当确定要转的变量一定有值的时候,可以将左边的可选类型转换为右边确定的类型,as,将左边的类型转换为右边的类型,例如:利用as将swift字符串转为oc字符串来实现字符串的截取 2:1:在封装的view中提供类方法快速返回xib中加载的对象:在swift中类方法以class开头:class + func + 函数名(参数裂变)-> 返回值类型{ 业务逻辑代码 return 返回值类型 } 2:代码 class func visitorView()-> RHVisitorView{ //1:加载xib的view let visitor = Bundle.main.loadNibNamed("RHVisitorView", owner: nil, options: nil)!.last as! RHVisitorView visitor.backgroundColor = UIColor(red: 238/255.0, green: 238/255.0, blue: 238/255.0, alpha: 1) return visitor } 1:Bundle.main.loadNibNamed("RHVisitorView", owner: nil, options: nil),从xib中获得加载的数组,此获得数组的类型为可选类型[Any]?,Any表示数组中存放的元素,此时可以保证数组一定有值,所以可以!强制解包, Bundle.main.loadNibNamed("RHVisitorView", owner: nil, options: nil)! 2:Bundle.main.loadNibNamed("RHVisitorView", owner: nil, options: nil)!.last,从数组或是字典取出的元素都是Any?的可选类型,能保证其一定有值,可以用as!转换为RHVisitorView类型 3:封装view提供方法接口来控制封装view具体显示的内容和需要隐藏的内容:1:传入参数imageName和title来设置图片和文字的显示内容,其中苹果默认函数中第一个参数为内部参数,用_+空格+参数名来表示,默认从第二个参数开始即为内部参数又为外部参数,要想让其他参数,也为默认参数则可以用_+空格+参数名来表示,还可以传入默认参数。 4:首页需要旋转图片:旋转图片绕着z轴旋转一圈2π,从0到2π,两个value值,所以用CABasicAnimation func rotationImage() { //1:创建基础动画对象 let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z") //2:设置动画效果 rotationAnimation.fromValue = 0 rotationAnimation.toValue = M_PI * 2 rotationAnimation.repeatCount = MAXFLOAT rotationAnimation.isRemovedOnCompletion = false rotationAnimation.duration = 3 //3:将动画添加到layear上 rotationImageView.layer.add(rotationAnimation, forKey: nil) } 注意:1:核心动画,都会在切换界面或是程序进入后台的时候停止动画,所以rotationAnimation.isRemovedOnCompletion = false 2:要将核心动画添加到view的layer上 **/ class RHVisitorView: UIView { //MARK:-1:定义xib属性 @IBOutlet weak var loginBtn: UIButton! @IBOutlet weak var infoLable: UILabel! @IBOutlet weak var registerBtn: UIButton! @IBOutlet weak var rotationImageView: UIImageView! @IBOutlet weak var iconImageView: UIImageView! //MARK:-2:类方法加载xib并设置背景色 class func visitorView()-> RHVisitorView{ //1:加载xib的view let visitor = Bundle.main.loadNibNamed("RHVisitorView", owner: nil, options: nil)!.last as! RHVisitorView visitor.backgroundColor = UIColor(red: 238/255.0, green: 238/255.0, blue: 238/255.0, alpha: 1) return visitor } //MARK:-3:提供接口,供子类调用 func setupVisitorView(_ imageName:String,title:String) { iconImageView.image = UIImage(named: imageName) infoLable.text = title rotationImageView.isHidden = true } //MARK:-4:旋转图片 func rotationImage() { //1:创建基础动画对象 let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z") //2:设置动画效果 rotationAnimation.fromValue = 0 rotationAnimation.toValue = M_PI * 2 rotationAnimation.repeatCount = MAXFLOAT rotationAnimation.isRemovedOnCompletion = false rotationAnimation.duration = 3 //3:将动画添加到layear上 rotationImageView.layer.add(rotationAnimation, forKey: nil) } }
2:父类控制器代码
import UIKit /* 总结: 1:为什么要抽父类RHBaseTableViewController?因为要显示访客视图就要判断用户是否登陆,四个控制器界面都需要判断,所以可以将四个界面相同的业务逻辑抽到父类,让子类去继承(若是将业务逻辑封装在工具类中,四个界面依然需要写相同的代码,所以考虑抽到父类,让子类去继承)四个界面的访客视图界面又是类似,所以可以将访客视图的界面也封装在父类中,访客视图封装时把尽可能显示的控件全部添加到视图中,然后父类提供访客视图的接口,让子类去继承,访客视图中再去封装方法来控制不同界面访客视图的显示和隐藏 2:在父类中定义属性标识来判断用户是否登陆:1:在类中定义属性的时候,必须给属性一个初始值,或是定义为可选类型,在之后初始化或是其他地方再给该属性赋值,Bool属性只有两个关键字true和false 2:懒加载:1:闭包懒加载 2:直接进行初始化 注意:1:懒加载用关键字lazy并且必须是var来修饰 2:在定义类属性或是懒加载或是定义类中方法的时候,要考虑是否需要用private或是filePrivate来修饰来保证私有不让外界访问,private,只在当前的class类中可以访问,在同一个文件中不同class类中不可以访问,filePrivate在当前文件任何地方都可以访问,二者都不能在外界被访问 3: 设置访客视图需要重写控制器的方法loadView,在此方法中可以设置控制器当前的view,当我们需要重写系统的方法的时候,需要加上override重写标识,用三目运算,根据是否登陆的表示,来选择加载不同的控制器的view,setupView()方法是创建访客视图的方法,一般将此方法都封装在当前类的扩展中extension override func loadView() { //1:根据标识判断是加载访客视图还是登陆后界面 isLogin ? super.loadView() : setupView() } 4: //MARK:-4:设置访客视图的view extension RHBaseTableViewController { fileprivate func setupView() { view = visitorView } },fileprivate设置该方法为私有,外界不可以访问 5:在viewDidLoad中设置左右导航栏按钮:将设置导航栏左右按钮也封装在当前类中的extension中,在当前类中调用当前类的方法可以省去self: //注册按钮 navigationItem.leftBarButtonItem = UIBarButtonItem(title: "注册", style: .plain, target: self, action: #selector(RHBaseTableViewController.clickRegisterBtn)) 事件监听:#selector(RHBaseTableViewController.clickRegisterBtn),一般也将事件的监听封装在当前类的extension中, 当监听点击事件的时候,才用 @objc,当swift中定义的方法用private或是fileprivate修饰的时候,则不会将该方法加载进类的方法列表,用@objc则会将该方法依然加入到方法列表中 extension RHBaseTableViewController { @objc fileprivate func clickRegisterBtn() { DLog(message: "点击了左侧的注册按钮") } 6:访客视图中的登陆注册按钮的监听:1:若是将监听方法放在访客视图的view中则需要协议代理,通知,闭包来进行反向监听,还可以在访客视图中提供两个btn的属性接口,在父类控制器中来设置监听,在父类的控制器中来实现跳转 **/ class RHBaseTableViewController: UITableViewController { //MARK:-1:定义属性标识:用来判断是显示访客视图还是登陆界面,默认是没有登陆 let isLogin = false lazy var visitorView = RHVisitorView.visitorView() //MARK:-2:设置访客视图:重写loadView方法,从而更改当前控制器的view override func loadView() { //1:根据标识判断是加载访客视图还是登陆后界面 isLogin ? super.loadView() : setupView() } override func viewDidLoad() { super.viewDidLoad() //MARK:-3:设置左右按钮的导航栏 setupNavgationItem() //MARK:-4:添加登录注册按钮监听 //注册按钮 visitorView.registerBtn.addTarget(self, action: #selector(registBtnClick), for: .touchUpInside) //登录按钮 visitorView.loginBtn.addTarget(self, action: #selector(loginBtnClic), for: .touchUpInside) } } //MARK:-4:设置访客视图的view extension RHBaseTableViewController { fileprivate func setupView() { view = visitorView } } //MARK:-5:设置导航栏左右item extension RHBaseTableViewController { fileprivate func setupNavgationItem() { //注册按钮 navigationItem.leftBarButtonItem = UIBarButtonItem(title: "注册", style: .plain, target: self, action: #selector(RHBaseTableViewController.clickRegisterBtn)) //登录按钮 navigationItem.rightBarButtonItem = UIBarButtonItem(title: "登录", style: .plain, target: self, action: #selector(RHBaseTableViewController.clickLoginBtn)) } } //MARK:-6:监听按钮的点击 extension RHBaseTableViewController { @objc fileprivate func clickRegisterBtn() { DLog(message: "点击了左侧的注册按钮") } @objc fileprivate func clickLoginBtn() { DLog(message: "点击了右侧的登录按钮") } @objc fileprivate func registBtnClick() { DLog(message: "点击了注册按钮") } @objc fileprivate func loginBtnClic() { DLog(message: "点击了登录按钮") } }
3:Appdelegate中设置Navbar的渲染
import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { //MARK:-1:设置全局tab渲染颜色 UITabBar.appearance().tintColor = UIColor.orange //MARK:-2:设置全局导航栏渲染颜色 UINavigationBar.appearance().tintColor = UIColor.orange return true } } //MARK:-1:定义全局打印 func DLog<T> (fileName:String = #file,function:String = #function,lineNum:Int = #line, message:T) { #if DEBUG let fileNameComponse = (fileName as NSString).lastPathComponent print("(fileNameComponse):(function):(lineNum):("打印内容"):(message)") #endif }
4:子类控制器中调用
import UIKit class HomeViewController: RHBaseTableViewController { override func viewDidLoad() { super.viewDidLoad() //MARK:-1:让iconImageView旋转 visitorView.rotationImage() } }
import UIKit class MessageViewController: RHBaseTableViewController { override func viewDidLoad() { super.viewDidLoad() //MARK:-1:更换图片和title,取消动画 visitorView.setupVisitorView("visitordiscover_image_message", title: "支持MJExtension,没毛病,关注MJExtension,有惊喜") } }