项目基本架构的搭建
来源:互联网 发布:网络管理需求分析 编辑:程序博客网 时间:2024/05/16 09:11
一、启动图片的设置
项目启动图片的设置有多种方式,但是通常情况下,都是用LaunchImage来管理的。具体的操作方式比较简单,但是一定要注意,当你设置LaunchImage作为启动图片时,一定不要忘记把Launch Screen File中的文字给删除,并且在运行程序之前,最好是把之前运行过的程序给删掉:
二、初始化项目
项目配置完成以后,通常情况下,需要重新划分结构。在iOS开发中,有多种架构可供选择,最常见的架构是MVC,它在软件开发过程中有着广泛的应用。由于MVC本身不是特别完美,后来又衍生出了MVP和MVVM架构。在这里,我们按照MVVM架构的思想对项目目录进行重新划分。
1、使用纯代码来搭建项目
来到General里面,把Main Interface里面的Main给删掉,来到AppDelegate中自己创建Window:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // 创建Window并制定它的frame window = UIWindow(frame: UIScreen.main.bounds) // 设置window的rootViewController window?.rootViewController = nil // 显示window window?.makeKeyAndVisible() return true }
此时如果运行程序,肯定是看不到window的,因为我们把它设置为nil。接下来需要自定义TabBarController。新建一个名为QFMainViewController的类,让它继承自UITabBarController,然后来到AppDelegate中,将其设置为窗口的根控制器:
// 设置window的rootViewControllerwindow?.rootViewController = QFMainViewController()
此时运行程序就可以看到窗口,只不过它现在还没有颜色,看到的只是黑乎乎的一片。接下来要给它添加子控制器。根据实际情况,在各模块下面的Controller文件夹中创建对应的子控制器,然后来到QFMainViewController的viewDidLoad中创建子控制器:
override func viewDidLoad() { super.viewDidLoad() // 设置TabBar的颜色(仅仅只是设置QFMainViewController中TabBar的颜色) tabBar.tintColor = UIColor.init(red: 202 / 255.0, green: 155 / 255.0, blue: 104 / 255.0, alpha: 1) // 创建子控制器(tabBar按钮对应的子控制器) let liveChildVc = QFLiveViewController() // 设置子控制器的属性 liveChildVc.title = "直播" // 设置子控制器的标题 liveChildVc.tabBarItem.image = UIImage(named: "live-n_25x19_") liveChildVc.tabBarItem.selectedImage = UIImage(named: "live-p_25x19_") // 包装导航控制器 let liveChildVcNav = UINavigationController(rootViewController: liveChildVc) // 添加子控制器 addChildViewController(liveChildVcNav)}
我们只是添加了一个子控制器,还有其它子控制器需要添加。但是,我们不能再像上面那样做了。重复的代码太多,需要抽一个方法来专门处理子控制器:
我们看到,系统自带了一个添加子控制器的方法。但是,它不满足我们的要求,因为我们要传的参数远不止一个。为此,需要自定义添加子控制器的方法:
override func viewDidLoad() { super.viewDidLoad() // 创建子控制器(tabBar按钮对应的子控制器) addChildViewController(childVc: QFLiveViewController(), title: "首页", imageName: "live") addChildViewController(childVc: QFRankViewController(), title: "排行", imageName: "ranking") addChildViewController(childVc: UIViewController(), title: "", imageName: "") // 占位用的 addChildViewController(childVc: QFFoundViewController(), title: "发现", imageName: "found") addChildViewController(childVc: QFMineViewController(), title: "我的", imageName: "mine")}// 添加子控制器fileprivate func addChildViewController(childVc: UIViewController, title: String, imageName: String) { // 设置子控制器的属性 childVc.title = title // 设置子控制器的标题 childVc.tabBarItem.image = UIImage(named: imageName + "-n_25x19_") // live-n_25x19_ childVc.tabBarItem.selectedImage = UIImage(named: imageName + "-p_25x19_") // live-p_25x19_ // 包装导航控制器 let childVcNav = UINavigationController(rootViewController: childVc) // 添加子控制器 addChildViewController(childVcNav)}
在OC中,我们不能像上面那样自定义方法,因为方法名相同,系统在发送消息时,不知道将其发给谁。但是,在Swift是可以的。因为Swift支持方法重载。所谓的方法重载,就是指方法名相同,但是参数不同。而参数不同又有两重含义,即参数的类型不同,以及参数的个数不同。另外,这个方法最好是私有的,其它地方的类应该是不能访问的,所以我们应该给它加上访问限制fileprivate。
在Swift中,与访问权限有关的关键字主要有4个,它们既可以修饰属性,也可以修饰函数,主要为:
1、internal : 表示内部的 ①、默认情况下,所有类、属性、函数的访问权限都是internal; ②、表示在本模块(项目\包\target)中都可以访问2、fileprivate : 表示在当前源文件中可以访(Swift 3.0之后出来的) ①、只有在当前文件中可以访问,而其它文件中是不能访问的3、private : 表示私有的 ①、只有在当前类中才可以访问,其它类中是不能访问的4、open : 表示公开的(在Swift 2.x中叫public) ①、可以跨模块进行访问
还有两点需要补充,第一个是设置全局的tintColor。因为每一个子控制器都需要设置tabBar的tintColor,所以我们最好是不要在各个子控制器类中单独设置,而是应该把它放在AppDelegate中进行设置:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // 设置全局TabBar的颜色 UITabBar.appearance().tintColor = UIColor.init(red: 202 / 255.0, green: 155 / 255.0, blue: 104 / 255.0, alpha: 1) // 与window有关的代码 return true}
TabBar正中间的那个item是用来占位的,以后上面需要添加一个按钮,所以这个item应该是不能点击的,所以我们这里先把它给禁用掉:
// 禁用占位控制器TabBar按钮的点击 override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // 遍历TabBarItem中的items for i in 0..<tabBar.items!.count { // 取出item let item = tabBar.items![i] // 将下标为2的item给禁用掉 if i == 2 { item.isEnabled = false break } } }
现在TabBar正中间的这个item已经不能点击了,后面直接在上面添加一个按钮,然后再监听它的点击就可以了。
2、通过字符串来初始化项目
在上面搭建TabBar子控制器的过程中,我们传递的是子控制器对象,接下来我们要用与子控制器对应的字符串来搭建TabBar。
修改我们刚才写的添加子控制器的代码,将子控制器对象参数修改为String类型,其它的不变:
// 添加子控制器fileprivate func addChildViewController(childVcName: String, title: String, imageName: String) { // 根据传进来的控制器字符串获取与之对应的class // 将AnyClass转成具体的控制器类型 // 根据具体的控制器类型来创建对应的子控制器}
修改创建子控制器的代码,将子控制器对应的字符串作为参数传递给添加子控制器的方法addChildViewController(childVcName: , title: , imageName: ):
override func viewDidLoad() { super.viewDidLoad() // 创建子控制器(tabBar按钮对应的子控制器) addChildViewController(childVcName: "QFLiveViewController", title: "首页", imageName: "live") addChildViewController(childVcName: "QFRankViewController", title: "排行", imageName: "ranking") // 占位时,这里不要用UIViewController addChildViewController(childVcName: "QFLiveViewController", title: "", imageName: "") // 占位用的 addChildViewController(childVcName: "QFFoundViewController", title: "发现", imageName: "found") addChildViewController(childVcName: "QFMineViewController", title: "我的", imageName: "mine")}
一般而言,只要有了与类对应的字符串,我们就能用NSClassFromString方法来创建对象。但是,Swift有一个地方比较特殊,需要先拿到项目的命名空间,然后再用命名空间拼接与类对应的字符串名称,这样我们才能创建相应的对象:
// 添加子控制器fileprivate func addChildViewController(childVcName: String, title: String, imageName: String) { // 获取项目的命名空间 guard let nameSpace = Bundle.main.infoDictionary!["CFBundleExecutable"] as? String else { // 如果命名空间获取失败,直接返回 return } // 根据传进来的控制器字符串获取与之对应的class(命名空间.子控制器的类名) guard let childVcClass = NSClassFromString(nameSpace + "." + childVcName) else { // 如果childVcClass获取失败,直接退出 return } // 将获取到的AnyClass转成具体的控制器类型 guard let childVcType = childVcClass as? UIViewController.Type else { // 如果转类型失败,则直接返回 return } // 创建对应的控制器对象 let childVc = childVcType.init() // 设置子控制器的属性 childVc.title = title // 设置子控制器的标题 childVc.tabBarItem.image = UIImage(named: imageName + "-n_25x19_") childVc.tabBarItem.selectedImage = UIImage(named: imageName + "-p_25x19_") // 包装导航控制器 let childVcNav = UINavigationController(rootViewController: childVc) // 添加子控制器 addChildViewController(childVcNav)}
有一个细节需要注意,因为中间发布直播是一个按钮,并不需要创建与之对应的子控制器类,在采用常规方式搭建时,我们用一个并未创建的UIViewController作为占位就可以了。但是,在使用子控制器类对应的字符串方法搭建TabBar时,不能再用这个实际并未创建的UIViewController作为占位了,而是要用一个已经创建了的类作为占位,比如说我们这里使用了QFLiveViewController这个类。
3、通过Json文件来初始化项目
其实通过Json文件来初始化项目跟通过字符串来初始化项目本质上一样的,只不过这个字符串不是在创建子控制器的时候传递进来的,而是通过一个json文件来获取的(比如说来自服务器的json文件),它在创建的时候,也是需要现在项目中创建对应的类,然后再动态的加载:
override func viewDidLoad() { super.viewDidLoad() // 通过json文件来初始化项目 setupFromJsonFile()}// 通过json文件来初始化项目fileprivate func setupFromJsonFile() { // 获取json文件的路径 guard let jsonPath = Bundle.main.path(forResource: "ViewController.json", ofType: nil) else { return } // 将json文件转成NSData guard let jsonData = NSData(contentsOfFile: jsonPath) else { return } // json序列化(这里要进行异常处理) guard let anyOb = try? JSONSerialization.jsonObject(with: jsonData as Data, options: .mutableContainers) else { return } // 将anyOb转成字典数组 guard let dictArr = anyOb as? [[String: Any]] else { return } // 遍历数组中的字典 for dict in dictArr { // 获取子控制器对应的字符串名称 guard let childVcName = dict["childVcName"] as? String else { continue } // 从字典中取出来的数据是一个Any可选类型,需要现将其转换成String可选类型,之后才能传给自定义子控制器的函数 // 获取子控制器对应的title guard let title = dict["title"] as? String else { continue } // 获取子控制器对应的背景图片名称 guard let imageName = dict["imageName"] as? String else { continue } // 拿到对应的字符串儿,添加子控制器 addChildViewController(childVcName: childVcName, title: title, imageName: imageName) }}
添加子控制器的代码不用改,只需要修改获取字符串的方式,然后再将从json文件中获取到的字符串传递给它就可以了。最后补充一点关于异常的知识点。如果在调用系统的某一个函数的过程中,该函数后面有一个throws,说明该函数会抛出异常,此时你需要对异常进行处理。在Swift中提供了三种处理异常的方式:
①、try方式:程序员手动捕捉异常,在真实的开发环境中用得很少; ②、try?方式:系统帮我们处理异常。如果该函数产生了异常,则返回nil; 如果没有异常,则返回对应的对象。也就是说,该方式会返回一个可选类型, 因此我们需要对结果进行安全校验,这个比较常用; ③、try!方式:直接告诉系统,该函数没有异常。但是,如果该函数真的产生了异常, 那么程序会崩溃,类似于强制解包,操作起来非常的危险,一般不建议使用
4、通过Storyboard来初始化项目
以前在开发的时候,使用得比较多的可能是纯代码,因为如果使用Storyboard,可能会因为界面过多而造成混乱。但是,实际上苹果幕后做了很多工作来推广Storyboard。在iOS 9中,苹果引入了Storyboard Reference这个概念,它允许你从segue中引用其他storyboard中的viewController。这意味中你可以保持不同功能模块化,同时Storyboard的体积变小并易与管理。下面我们就用一下Storyboard Reference。
来到Main.storyboard文件,将里面的控制器给删掉,往里面拖一个UITabBarController控制器,并且让它成为默认的控制器(勾选is initial View Controller)。UITabBarController自带了两个子控制器,但是它不是我们想要的,直接把它们给删除:
选中TabBarController,把它交给QFMainViewController来管理,然后去AppDelegate中把我们写的窗口相关的代码删掉,最后再去General中设置Main Interface从Storyboard中启动:
回到Main.storyboard文件中,往里面拖4个NavigationController,以及一个用来占位的ViewController,然后右击TabBarController,将viewControllers分别拖给这几个子控制器,具体操作如下图所示:
现在里面控制器非常多,是不是看起来很乱?不过不要紧,我们可以把它们拆分成单独的Storyboard文件。选中其中一个子控制器,然后点击菜单栏上面的Editor,之后选择Refactor to Storyboard。具体操作如下图所示:
点击完Refactor to Storyboard之后会弹出一个对话框,给新的Storyboard文件取一个名字,然后点击保存就可以了:
按照同样的方式,分别处理其它几个子控制器,占位用的ViewController暂时不用管。处理完之后,Main.storyboard文件中大概就是这个样子:
现在看起来就非常简洁了,我们可以在不同的子控制器所对应的Storyboard文件中处理具体的问题。不过,需要说明的是,Storyboard Reference不支持iOS 8.0及其以下的版本。如果你希望支持iOS 8.0,最好是用纯代码来搭建。
最后是进行一些细节的处理,设置子控制器tabBarItem的图片和标题。然后再来到QFMainViewController的viewDidLoad方法中,添加中间的发布按钮:
// 中间发布直播按钮懒加载fileprivate lazy var homePageBtn : UIButton = UIButton()override func viewDidLoad() { super.viewDidLoad() // 添加中间的发布按钮 setupHomePageBtn()}// 添加中间的按钮fileprivate func setupHomePageBtn() { tabBar.addSubview(homePageBtn) // 设置中间按钮的图片 homePageBtn.setImage(UIImage(named: "homepage_btn_play_n_67x55_"), for: .normal) // 设置按钮的尺寸 homePageBtn.sizeToFit() // 设置按钮的位置(将发布直播的按钮添加到TabBar正中间) homePageBtn.center = CGPoint(x: tabBar.center.x, y: tabBar.bounds.size.height * 0.5)}
接下来,我们要监听发布直播按钮的点击。但是在此之前,我们先来补充一点便利构造函数的知识。
根据给定的图片来创建一个按钮,像这种需求在项目中经常碰到,所以最好是单独给它抽取一个方法。以前在OC中,这种情况一般是给UIButton抽一个分类。但是,Swift中基本上没有分类这个概念。不过,我们依然可以给系统的类来增加分类方法。新建一个Swift File文件,名字可以随便取,但是最好取一个见名知意的名字。然后导入UIKit框架,给UIButton写一个extension扩展:
extension UIButton { /// 类方法,根据给定的图片创建一个按钮(不是最好的选择) class func createButton(imageName: String, backgroundImageName: String) -> UIButton { // 创建按钮 let button = UIButton() /** 设置按钮的属性 */ // 设置按钮的图片 button.setImage(UIImage(named: imageName), for: .normal) button.setImage(UIImage(named: imageName + "highlighted"), for: .highlighted) // 设置按钮的背景图片 button.setBackgroundImage(UIImage(named: backgroundImageName), for: .normal) button.setBackgroundImage(UIImage(named: backgroundImageName + "_highlighted"), for: .highlighted) // 设置按钮的尺寸 button.sizeToFit() return button }}
现在在外面你就可以通过UIButton调用类方法来创建按钮了。但是,这是OC喜欢干的事儿,它不是真正的Swift。在Swift中,创建对象一般都是使用构造函数,所以我们也应该用构造函数。
在Swift中,要对系统类的构造函数进行扩充,一般是使用便利构造函数。用convenience修饰的构造函数叫做便利构造函数,它一般是写在extension里面,并且需要明确调用self.init()。下面我们就用便利构造函数来改造上面的代码:
extension UIButton { convenience init(imageName: String, backgroundImageName: String) { self.init() /** 设置按钮的属性 */ // 设置按钮的图片 setImage(UIImage(named: imageName), for: .normal) setImage(UIImage(named: imageName + "highlighted"), for: .highlighted) // 设置按钮的背景图片 setBackgroundImage(UIImage(named: backgroundImageName), for: .normal) setBackgroundImage(UIImage(named: backgroundImageName + "_highlighted"), for: .highlighted) // 设置按钮的尺寸 sizeToFit() }}
现在我们在外面创建按钮时,可以直接使用按钮的便利构造函数了,直接将图片名作为参数传递进去,高亮背景图片因为没有,所以可以传空:
// 中间发布直播按钮懒加载fileprivate lazy var homePageBtn : UIButton = UIButton(imageName: "homepage_btn_play_n_67x55_", backgroundImageName: "")override func viewDidLoad() { super.viewDidLoad() // 添加中间的发布按钮 setupHomePageBtn()}// 添加中间的按钮fileprivate func setupHomePageBtn() { tabBar.addSubview(homePageBtn) // 设置按钮的位置(将发布直播的按钮添加到TabBar正中间) homePageBtn.center = CGPoint(x: tabBar.center.x, y: tabBar.bounds.size.height * 0.5)}
接下来是监听发布直播按钮的点击。来到添加发布直播按钮的方法中,调用addTarget(, action: , for: )方法,然后再给QFMainViewController写一个extension,专门用来处理事件的监听:
// 添加中间的按钮fileprivate func setupHomePageBtn() { // 添加homePageBtn的代码 // 监听发布直播按钮的点击 homePageBtn.addTarget(self, action: #selector(QFMainViewController.homePageBtnClick), for: .touchUpInside)}// MARK: - 事件监听extension QFMainViewController { @objc fileprivate func homePageBtnClick() { // print("QFMainViewController.homePageBtnClick") }}
发布直播按钮监听的方法应该只属于QFMainViewController这个类,不应该让其它类来访问。但是,一旦添加了fileprivate访问限制,系统就会报找不到方法(unrecognized selector sent to instance)这个错误,解决的办法是在前面加上@objc属性。其实,事件监听本质上是发送消息,而发送消息是OC的特性。在OC中,发送消息的步骤是,先将方法包装成@SEL,然后再去类中查找方法列表,根据@SEL找到imp指针(也就是我们这个对应的函数指针),之后就是执行这个函数。如果在Swift中将函数声明成fileprivate,那么该函数不会被添加到方法列表中。但是,如果在前面再加上@objc属性,这个函数就会被添加到方法列表中。
- 项目基本架构的搭建
- WEB项目的基本架构
- 1.Java基本架构的搭建
- android中,项目架构的搭建
- 如何搭建SoC项目的基本Testbench
- Android项目架构搭建
- Android项目架构搭建
- Android项目架构搭建
- 搭建前端项目架构
- MySQL-MS基本架构搭建
- angularJS搭建简单应用程序的基本架构分析
- 基于多层架构的asp.net mvc项目的搭建
- SSM框架之SpringMVC(2)--web项目的基本架构
- 金赢网项目架构基本完成
- ReactNative 开发基本项目架构
- android app项目启动时的架构搭建
- MVP架构实现的Github客户端(2-搭建项目框架)
- Android项目架构搭建必备
- SDWebImage的基本使用
- SDWebImage框架重要的细节
- NSCache的基本使用
- RunLoop的基础知识
- 《Swift数据结构和算法》读书笔记专题
- 项目基本架构的搭建
- Swift基础知识补充(一)
- Swift基础知识补充(二)
- Swift基础知识补充(三)
- Swift中的闭包
- Swift中的枚举
- Swift中的访问级别
- Swift中的属性
- Swift中的构造方法