当父UIViewController打开一个新的子UIViewController时, 二者之间可能需要双向传值即父传子、子回传父, 或者单向传值的父传子、子传父。
我整理了一下,大概分为7种方法(前3种方法较常用):
1、UIStoryBoardSegue, 前置条件是要使用storyboard做界面。
2、闭包, 即在父窗口实现闭包, 在子窗口保存闭包的引用。
3、协议, 跟闭包原理一样, 即在父窗口实现协议接口, 在子窗口保存协议引用。
4、单例模式, 即父/子界面访问同一个单例。
5、文件持久化, 例如Core Data、UserDefaults或其他三方数据库; 原理跟单例模式一样, 区别是单例模式读写的是内存。
6、网络接口, 即父/子窗口与网络服务器同步数据, 原理跟单例模式、文件持久化一样, 区别是将数据保存到网络。
7、 观察者模式, 即在UIViewController里注册监听对应的Notification; 例如父窗口观察, 子窗口修改值。一、 使用UIStoryBoard画界面是比较基础的行为,一般被认为是iOS开发入门级。 在讲述值传递前先看一下UIViewController的生命周期。
父UIViewController打开子UIViewController:
父界面prepare
父窗口viewWillDisappear
子界面viewDidLoad
子窗口viewWillAppear
父窗口viewDidDisappear
子窗口viewDidAppear
子界面 prepare
unwindToMain
子窗口viewWillDisappear
父窗口viewWillAppear
子窗口viewDidDisappear
父窗口viewDidAppear
/**
* 父窗口向子窗口传值
* segue, 判断下一级UIViewController类型
* sender, 触发切换ViewController的地方,在这里是按钮控件
*/
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("父界面prepare")
//方法一 通过目标UIViewController类型判断
if let vc = segue.destination as? SecondeViewController {
vc.value = "第二级界面111"
//print("显示SecondViewController")
}
//方法二 通过点击的控件判断
if sender as? UIButton == btnNext {
//print("点击按钮显示二级界面")
}
}
上面代码是在显示子窗口SecondViewController时, 通过segue参数的destination属性为子窗口赋值, 即实现了父传子(类似于Android startActivity时在intent里携带参数)。
子窗口SecondViewController在退出后需要传值给父窗口ViewController, 实现方法是在父窗口实现一个unwindToMain函数且在uistoryboard里将exit指向该函数。 具体如下:
//从子ViewController返回, 作用类似于Android的onAcitivityResult函数
@IBAction func unwindToMain(_ segue: UIStoryboardSegue) {
print("unwindToMain")
if let vc = segue.source as? SecondeViewController {
print(fatherName)
print("\(vc.age)") //子窗口给父窗口的传值, 在父窗口里能拿到子窗口的引用!
}
}
子UIViewController在退出前可以给父ViewController赋值, 或者将控件值缓存到变量里。
//退出当前界面时 保存值到变量里
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//退出该界面时执行
print("子界面 prepare")
if let vc = segue.destination as? ViewController {
print("返回上一级界面")
vc.fatherName = "子窗口修改父窗口值" //子ViewController也有父Controller的引用,能够修改父controller的值,但不建议这样做!!!
//将本界面控件的值保存到变量里
value = "二级界面返回值"
age = 100
}
}
二、 闭包, 在这里应该是逃逸闭包。 在子界面里保存闭包的引用, 在父窗口实现闭包。 下面的写法有点像Java, 其实可以声明个构造函数,将闭包作为构造函数的一个参数。
class SecondeViewController: UIViewController {
.......
var callback: (_ name: String?, _ age: Int?) -> Void? = {(name, age) in
print("默认闭包")
return nil
}
//设置闭包
func setCallBack(callback: @escaping (String?, Int?) -> Void?) {
self.callback = callback
}
...
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//退出该界面时执行
print("子界面 prepare")
callback("张三", 100) //通过闭包回调通知
}
if let vc = segue.destination as? SecondeViewController {
vc.value = "第二级界面111"
vc.setCallBack(callback: { (name, age) in
print("name: \(name), age: \(age)") //子界面的返回值
})
}
子界面 prepare
name: Optional("张三"), age: Optional(100)
unwindToMain
三、 协议, 跟Java的interface一模一样, 父类实现接口函数,子类保存接口的引用。
protocol SecondDelegate {
func respond(name: String?, age: Int?)
}
在父类里实现protocol:
class ViewController: UIViewController, SecondDelegate {
。。。
/**
* 父窗口向子窗口传值
* segue, 判断下一级UIViewController类型
* sender, 触发切换ViewController的地方,在这里是按钮控件
*/
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? SecondeViewController {
vc.value = "第二级界面111"
vc.delegate = self
}
}
func respond(name: String?, age: Int?) {
print("接口返回: \(name), \(age)")
}
。。。
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//退出该界面时执行
print("子界面 prepare")
delegate?.respond(name: "李四", age: 200)
}
class SingleTon {
static let instance = SingleTon()
var name: String?
var age: Int?
}
五、 持久化, 即从文件里读写数据。 包括iOS提供的Core Data, UserDefaults、文件或其他三方框架等。 不同界面读写同一个文件, 因为文件IO比内存读写更浪费资源也更慢, 要注意使用的时机。
六、 网络接口, 这个很好理解,就是数据存储在服务器上。 现在的互联网app都会联网, 那么A界面上传到服务器的数据, B界面在使用时可以从服务器下载。 PS: 网络交互会遇到各种情况, 要跟进业务逻辑使用这种方式。 原则是以服务器的数据为准!!! 避免发生数据不同步的问题。
七、 观察者模式, 就是使用iOS的NotificationCenter注册监听, 当数据变化时接收到回调。 注意iOS和Android的区别: Android一般会在onStop函数里unregister监听、onResume函数里注册监听, 即只有当前活跃窗口保持监听; 而iOS是只要界面还存在, 那么就保留监听,直接界面销毁时才取消监听。 PS: 原因是Android的内存管理机制,后台的activity很可能被gc回收,如果不在onStop函数里释放资源就可能内存泄漏; 而iOS的内存管理更强大,一般不会出现内存溢出,这就是Swift的强大之处!
iOS没提供类似于Android startActivityForResult的功能, 日常开发中一般使用代码画界面, 所以UIViewConroller之间的数据交换一般用闭包或协议实现。
参考代码:http://download.csdn.net/detail/brycegao321/9916397