iOS中具有复杂UI的模块的设计

96
windgo
2015.09.11 17:21* 字数 1080

iOS中复杂UI模块的设计

复杂问题的解决方法

复杂问题的解决方法就是分解,将复杂性一步步分解,复杂问题分解成一系列复杂性较低的可接受的小问题。

开发拥有复杂UI的模块,关键的思路也是分解。

为了更形象,我们拿切土豆丝这件事作为比喻。厨师是怎么切土豆丝的?

完整的土豆通常因为太大,难以吃下,所以我们最常见的土豆的食品,在中国是土豆丝,在国外估计是薯条,其实都是土豆丝。土豆丝的切法,一般来说是先要切片,然后切丝。从两个方向的切,刀的轨迹交织成一个网格,网格里每一个网眼,就是一根土豆丝。

编写程序也一样,对于复杂的功能,需要分解成一根根代码的土豆丝。而且分解的过程也很神奇的和切土豆丝类似,先切片,也就是分层,然后再切丝,也就是划分模块。

1. 切片和分层

iOS开发中的切片,就是分层,常见的是按照MVC(最近还有mvvm什么的)来划分。


控制器:UIViewController的各种子类 ↓(依赖下面的两个层)


视图: UIView子类,Storyboard,Xib文件等


模型:各种表示业务逻辑的类,数据结构的,网络请求的,存储的等等


分层的关键,是解除双向依赖。

两个层之间的代码,上面的层依赖下面的层,下面的层不依赖上面的层;上面层会直接调用下面层中的代码,下面的层不会直接调用上面层中的代码(可以通过kvo,通知,delegate,block等技术间接发送消息)。

在MVC下,就是C层依赖V层,C层依赖M层。解除双向的依赖之后,问题的复杂度就下降了不少。双向依赖容易导致代码死循环,对象引用循环等,在业务的职责上,也容易出现踢皮球的现象。

常常有一种情况,以为自己分层了,其实没有分层,只是分模块。比如,一个登陆模块,你只是分成Login.storyboard, LoginViewController.swift, LoginModel.swift。这不见得是分层,可能只是分了模块。

//LoginViewController.swift
class LoginViewController:UIViewController{
    var loginModel=LoginMode() //控制器
    @IBAction func onLoginButton(button:UIButton!){
        model.login(nameTextField.text,passwordTextField.text,self)
    }
    func loginSucess(){
        //登陆成功之后界面的跳转
    }
}

//LoginModel.swift
class LoginModel {
    func login(name:String,password:String,viewController:LoginViewController) {
        if checkLoginInfo(name,password) == true {
            viewController.loginSucess()
        }
    }
}

因为model和controller发生了相互调用,M和C之间相互依赖了,所以不算分层。分层的写法是这样:

//LoginViewController.swift
class LoginViewController:UIViewController,LoginDelegate{
    var loginModel=LoginMode() //控制器
    @IBAction func onLoginButton(button:UIButton!){
        model.login(nameTextField.text,passwordTextField.text,self)
    }
    func onLoginSuccess(){
        self.loginSucess()
    }

    func loginSucess(){
        //登陆成功之后界面的跳转
    }
}

//LoginDelegate.swift
//使用协议,来解除模型对控制器的依赖
protocol LoginDelegate:class {
    func onLoginSuccess()
}

//LoginModel.swift
class LoginModel {
    weak var delegate:LoginDelegate? //利用代理模式,解除了依赖。
    func login(name:String,password:String) {
        if checkLoginInfo(name,password) == true {
            if let d=delegate {
                d.loginSucess()
            }
        }
    }
}

2. 切丝和功能模块的分解

功能模块的分解,有两个标准,一个是从空间上,一个是从时间上。
假设有这样一个功能,要在一个view里,展示一个班级的信息,包括班级的名称,成立时间,人数,人员列表(可能是学生,也可能是老师),选中人员,展示人员的信息(包括老师的,学生的)

总视图

这样一个功能的UI,从空间上可以分解成:

  • 班级信息
  • 人员列表
  • 人员详细信息
    三部分界面
    其中,人员详细信息部分的界面,从时间上又可以分为:
  • 学生详细信息
  • 教师详细信息
    那么,最终我们分解出的结果如下:

1. 视图层

storyBoard里面包括:
总视图:(对应控制器:SchoolClassViewController)

界面分解我喜欢用的一个技巧,是创建一些占位的试图,到时候子视图直接加入到这些占位视图里面就可以了,子视图的frame,按照占位视图的bounds设置就可以,很方便。

总视图

班级信息视图:(对应控制器:ClassInfoViewController)

班级信息

成员列表视图:(对应控制器:MembersViewController)

成员列表视图

学生详细信息视图:(对应控制器:StudentViewController)

学生详细信息视图

教师详细信息视图:(对应控制器:TeacherViewController)

教师细信息视图.png

2. 控制器层:

//视图总控制器
class SchoolClassViewController:UIViewController {
    @IBOutlet var classInfoViewContaner:UIView! //班级信息占位视图
    @IBOutlet var membersContaner:UIView! //成员列表占位视图
    @IBOutlet var memberDetailContaner:UIView! //成员详细信息占位视图

    var classId=0

    var model:SchoolClassModel!
    func viewDidLoad(){
            model=schoolClassModel(classId);
            //班级信息控制器
            var classInfoViewController
                =ClassInfoViewController() 
            classInfoViewController.model
        =model
            self.addChildViewController(classInfoViewController)
            self.classInfoViewContaner.addSubView(classInfoViewController.view)
            //成员列表控制器
            var membersViewController=MembersViewController()
            membersViewController.model=model.memberList
            self.addChildViewController(membersViewController)
            self.membersContaner.addSubView(membersViewController.view)
    }
    func showMemberDetail(){
        if(model.selectedMember is Student){
                var studentViewController=StudentViewController()
                studentViewController.model=model.selectedMember
                self.addChildViewController(studentViewController)
                self.memberDetailContaner.addSubView(studentViewController.view)    
            }
            else if(model.selectedMember is Teacher){
                var teacherViewController=TeacherViewController()
                teacherViewController.model=model.selectedMember
                self.addChildViewController(teacherViewController)
                self.memberDetailContaner.addSubView(teacherViewController.view)    
            }
    }
}

//班级信息控制器
class ClassInfoViewController:UIViewController{
    weak var model:SchoolClass!
}
//成员列表控制器
class MembersViewController:UIViewController{
    var model:[Member]!
}
//学生详细信息控制器
class StudentViewController:UIViewController{
    var model:Student!
}
//教师详细信息控制器
class TeacherViewController:UIViewController{
    var model:Teacher!
}

3. 模型层

class SchoolClass{
    var name:String!
    var createTime:NSDate!
    var graduateTime:NSDate!
    var memberCount:Int{
        get {
            return count(memberList)
        }
    }
    var memberList=[Member]()
    var selectedMember:Member!
}
class Member{
    var name:String!
}
class Student:Member{
    …
}
class Teacher:Member{
    …
}

ios私房菜
Web note ad 1