jamiechoo

 

Swift中的7种界面传值方式总结(属性传值,构造器传值,通知传值,单例传值,协议传值,闭包传值,NSUserDefaults传值)

之前,我整理过OC的5种基本界面传值方式,很多朋友都有很喜欢,今天再整理一下Swift中的7种常用界面传值供大家参考,不到之处,欢迎各位提点,更多的希望能对大家有所帮助.
注: 文中 ,第一个界面为ViewController,第二个界面为DetailViewController两个界面都会分别有一个Button和一个TextFiled 用于输入值和接受值.来查看传值效果
两个界面的简单搭建及跳转操作如下
//第一个界面ViewController
import UIKit
class ViewController: UIViewController {
fileprivate var tf:UITextField = UITextField()
  override func viewDidLoad() {
    super.viewDidLoad()

    setUpUI()

  }
 //点击进入第二个界面的按钮进行跳转操作
 @objc fileprivate func btnAction()  {
    let vc = DetailViewController()
    navigationController?.pushViewController(vc, animated: true)
  }

  }

//设置界面
extension ViewController{
 fileprivate func setUpUI() {
    let btn = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 30))
    btn.center = self.view.center
    btn.backgroundColor = UIColor.cyan
    btn.addTarget(self, action: #selector(btnAction), for: .touchUpInside)
    btn.setTitle("进第二个界面", for: UIControlState.normal)
    self.view .addSubview(btn)
    tf = UITextField(frame: CGRect(x: 0, y: 0, width: 100, height: 30))
    tf.center = self.view.center
    tf.frame.origin.y = 200
    tf.backgroundColor = UIColor.blue
    self.view.addSubview(tf)
  }

}


 //第二个界面DetailViewController

import UIKit

class DetailViewController: UIViewController {
    var tf:UITextField!

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = UIColor.white
    setUpUI()
}

//点击按钮返回上一界面
func btnAction()  {
 navigationController?.popViewController(animated: true)

}


extension DetailViewController{

fileprivate func setUpUI() {
  
    let btn = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 30))
    btn.center = self.view.center
    btn.backgroundColor = UIColor.cyan
    btn.addTarget(self, action: #selector(btnAction), for: .touchUpInside)
    btn.setTitle("返回第一个界面", for: UIControlState.normal)
    self.view .addSubview(btn)
    tf = UITextField(frame: CGRect(x: 0, y: 0, width: 100, height: 30))
    tf.center = self.view.center
    tf.frame.origin.y = 200
    tf.text = tfStr
    tf.backgroundColor = UIColor.blue
    self.view.addSubview(tf)
}
}
上面👆界面搭建,下面进行的几种传值操作都是以这两个界面为基础分别进行的
1.属性传值

属性传值的方式特别简单,其实现的基本思路,就是为创建对象并为其属性赋值,同样的,我们模拟从第一个界面传值到第二个界面,首先我们在第二个界面声明一个String类型的tfStr属性。

import UIKit
class DetailViewController: UIViewController {
var tf:UITextField!
 //String类型的tfStr属性
var tfStr:String!
  
 override func viewDidLoad() {
    super.viewDidLoad()
  }
}

然后在处理第一个界面跳转到第二个界面的方法中实例化第二个界面对象,并为其属性赋值,如下所示

func btnAction()  {
let vc = DetailViewController()
    vc.tfStr = tf.text
 navigationController?.popViewController(animated: true)

}

接下来,我们即可直接在第二个界面中的viewDidLoad()方法中如下操作实现传值。

 tf.text = tfStr

👆你以学会属性传值

2.构造器传值

构造器传值,类似于OC的自定义初始化传值,即在初始化某一个视图控制器的时候,将需要传递的数据传递过去,因此,我们同样可以模拟从第一个面传值到第二个界面。
首先,我们需要在第二个界面中,自定义构造器,具体实现方式如下

class DetailViewController: UIViewController {
var tfStr:String!
var tf:UITextField!
override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = UIColor.white
    setUpUI()
    
}

 // 析构器

deinit {
    print(#function)
}
// 自定义构造器
init(text:String) {
    print(#function)
    print(text)
    tfStr = text
    // 指定构造器必须调用它最近父类的指定构造器
    super.init(nibName: nil, bundle: nil)
}
// init(coder aDecoder: NSCoder) 方法是来自父类的指定构造器, 因为这个构造器是 required, 必须要实现。但是因为我们已经重载了 init(), 定义了一个指定构造器, 所以这个方法不会被继承, 需要要手动覆写。

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}



func btnAction()  {

 navigationController?.popViewController(animated: true)

  }

}

自定义构造器创建好之后,我们即可在第一个界面处理界面跳转的方法中通过自定义构造器创建对象并传值了,如下所示

//点击钮进行跳转操作方法

@objc fileprivate func btnAction()  {
    let vc = DetailViewController(text: tf.text!)
    navigationController?.pushViewController(vc, animated: true)
    
}

👆你以学会构造器传值

3.通知传值

我们来看看通知传值,通知可实现任意界面之间的数据传递,但必须满足一个条件,就是保证在发送通知的时候监听者已经存在(先要注册通知)。而通知的注册与OC写法有所不同Swift种主要通过NotificationCenter 而不是NSNotificationCenter通知中心实现(其实就是写法不同),其为一个系统单例,系统提供了default方法获取通知实例对象。

通知使用步骤:注册通知 -> 发送通知 -> 移除通知

通知实现的原理,我们可以这样去理解,学生监听下课铃声。我们把学生看做监听者(或者叫观察者),监听铃声,铃声一响就放学。当铃声响起时,我们看做发出一个通知(信号),学生在监听到铃声之后就会做出相应的操作,比如放学之后做什么……

接下来我们看看通知传值的具体实现方式。这里我们模拟从第二个界面传值到第一个界面

首先我们需要在第一个界面注册通知,因为,程序运行肯定是先到第一个界面中,所以,当在第二个界面发送通知的时候,通知监听者肯定是存在的。注册通知的方法常用的有以下两种:

//1.    open func addObserver(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name?, object anObject: Any?)

//2.    open func post(name aName: NSNotification.Name, object anObject: Any?, userInfo aUserInfo: [AnyHashable : Any]? = nil)

第1种,我们需要通过Selector参数设置接收到通知时触发的方法。
而第2种,我们无需关联触发方法,在方法尾部跟着一个闭包,当接收到通知的时候该闭包会自动调用,我们可直接在闭包内处理相应的逻辑即可。第2种方法还有一个参数queue,该参数主要设置通知触发方法执行的队列,其为OperationQueue类型的对象,这里我们一般在主队列执行,配置参数方法为OperationQueue.main。我们可以直观的看到,在两种方法中都有一个name参数,该参数我们可以理解为通知的代号,通过这个代号我们可以避免多个通知串联,这个参数我们可以赋值任意字符串。我们这里实例需要传值而不是接到通知去进行一个事件所以这里以第2种为例。

按照上面提到的通知使用步骤:
(1)注册通知
注册通知我们在第一个界面中

 NotificationCenter.default.addObserver(forName: NSNotification.Name?, object: Any?, queue: OperationQueue?) { (Notification) in
        //拿到Notification内容进行逻辑处理....
    }

(2)发送通知
通知注册好之后,下一步我们就可以在第二个界面发送通知了,我们在处理界面跳转(返回)的方法中处理这一逻辑。发送通知主要用到以下方法:

open func post(name aName: NSNotification.Name, object anObject: Any?, userInfo aUserInfo: [AnyHashable : Any]? = nil)

这里需要注意,发送通知的aName参数,必须和注册通知时的name参数一致,否则在第一个界面将无法接收到通知。我们可通过aUserInfo参数将需要传递的数据传递到第一个界面中,该参数为一个[NSObject : AnyObject]?(字典)类型的数据。实现示例如下:

 func btnAction()  {

    NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notifyName"), object: nil, userInfo: ["text": self.tf.text])
     navigationController?.popViewController(animated: true)

}

当用户点击返回按钮时,发送通知,第一个界面接收到对应通知之后将会回调闭包,我们可在闭包中获得数据,如下所示:

 class ViewController: UIViewController {
  fileprivate var tf:UITextField = UITextField()
  override func viewDidLoad() {
    super.viewDidLoad()

    NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "notifyName"), object: nil, queue: OperationQueue.main) { (text) in
//这里拿到通知传来的数据text,然后进行逻辑处理,我们这里是为tf.text 赋值
        self.tf.text = "\(text.userInfo?["text"])"
    }
    setUpUI()
 
}

(3)移除通知
我们已经基本实现通知传值了,我们还需要最后一步,移除通知,通知的移除我们可在界面释放的方法(析构方法)中去执行,如下所示:

deinit {
    NotificationCenter.default.removeObserver(self)
}

👆你以学会通知传值

4.单例传值

单例,我们可以简单理解为“一个类,一个实例”。因此,不管我们在什么地方创建单例,所得到的都是同一个实例,根据这一特点,我们可通过单例进行传值,但是注意的是,单例传值,在我们获取单例值的时候首先必须保证单例确实有值,因此,我们可模拟通过单例实现从第一个界面传值到第二个界面的场景。
要使用单例传值,我们必须得创建单例,首先我们需要建立一个Swift文件,建立步骤:command + n -> iOS ->Swift File,为其取名为Singleton。然后我们在该文件中构造单例类,具体构造方式如下:

import Foundation
class Singleton{
 // 单例属性,用于传值;
var text : String!
static let singleton = Singleton()
// 获取单例实例方法
func share() -> Singleton {
    return .singleton
}

// 私有化init初始化方法,防止通过此方法创建对象
private init(){
  }

}

这里需要注意的是,我们可将需要传递的数据设置成单例的属性,如上述代码中的text属性。在第一个界面为其赋值之后我们就可以在第二个界面访问了

//第一个界面赋值
 override func viewDidLoad() {
    super.viewDidLoad()

    setUpUI()
    let singleton = Singleton.singleton.share()
    singleton.text = "我叫MangoJ"
    
}
//第二个界面任意位置都可获取单例属性的值本例在viewDidLoad中
 override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = UIColor.white
    tfStr = Singleton.singleton.share().text
    print(tfStr)
    setUpUI()
}

👆你以学会单例传值

5.协议传值

协议传值,主要用于代理模式。假设我们要实现从第二个界面传值到第一个界面这一需求,首先,我们需要拟定一份协议,为了方便,我们可直接在第二个界面中拟定协议,如下所示:
基本步骤:
1.声明协议
2.设置协议中的方法
3.声明代理

import UIKit

//1.声明协议
@objc protocol DetailViewControllerDelegate {
//2.设置协议中的方法
  @objc optional func viewController(viewController:DetailViewController,popWithValue:String)->Void
}

@objc关键字标识该协议为一个可选协议;optional关键字标识该协议方法对于协议的遵守者而言不是必须实现的。

声明了协议之后,我们需要为第二个界面声明一个代理属性,如下所示:

class DetailViewController: UIViewController {
//3.声明协议属性
 // 代理属性delegate值为实现了DetailViewControllerDelegate协议的任意对象,weak关键字主要为了防止循环引用导致对象无法释放。
weak var delegate : DetailViewControllerDelegate!
  

var tfStr:String!
var tf:UITextField!

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = UIColor.white
   
    setUpUI()
}

func btnAction()  {

 navigationController?.popViewController(animated: true)

  }

}

声明了代理属性之后,我们需要在处理界面跳转(返回)的方法中处理协议传值的逻辑了。首先我们需要判断代理人是否存在,可通过可选绑定来操作,如果代理存在,则让代理执行协议方法,并且将需要传递的信息通过参数传递给代理所在的界面,如下所示:

func btnAction()  {

    if let delegate = self.delegate {
        delegate.viewController!(viewController: self, popWithValue: "MangoJ")
    }
    
 navigationController?.popViewController(animated: true)
    
}

切换到第一个界面中,在处理界面跳转的方法中,我们将第二个界面的代理属性设为第一个界面,如下所示:

  //点击进入第二个界面的按钮进行跳转操作
 @objc fileprivate func btnAction()  {
  
    let vc = DetailViewController()
    //设置代理
    vc.delegate = self
    
    navigationController?.pushViewController(vc, animated: true)
    
}

然后,实现协议方法,在协议方法中,我们可以直接获取从第二个界面传递过来的值。

 // MARK:- DetailViewControllerDelegate -  
 func viewController(viewController: DetailViewController, popWithValue: String) {
    tf.text = popWithValue
}

👆你以学会代理传值

6.闭包传值

闭包主要用于回调,这里我们还是模拟从第二个界面传值到第一个界面,首先我们需要在第二个界面为闭包取个别名,声明一个闭包类型,如下所示:
使用闭包的步骤:
1.声明闭包类型
2.声明闭包属性
3.设置闭包传值调用方法
4.赋值闭包属性

import UIKit

class DetailViewController: UIViewController {
 // 1、声明闭包类型
typealias ClosureName = (String)->()
// 2、声明闭包属性
var closure : ClosureName!

var tfStr:String!
var tf:UITextField!

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = UIColor.white
   
    setUpUI()
}

 3.设置闭包传值调用方法
func callBack(closure:@escaping ClosureName)  {
    self.closure = closure
}

func btnAction()  {
  
      5、可选绑定:判断closure属性是否不为nil,如果不为nil,则通过closure将文本信息回调到调用closure方法所在的控制器中;
    guard (self.closure != nil) else {
        return
    }

    closure(tf.text!)
    
 navigationController?.popViewController(animated: true)

  }

}

现在第二个界面已经配置完毕,最后一步,我们在第一个界面推送到第二个界面的方法中,通过实例化的第二界面对象,调用闭包回调方法,然后打印数据即可,该方法在第二界面返回到第一界面的时候会直接被调用,代码如下

 //第一个界面的如下方法中
@objc fileprivate func btnAction()  {
  
    let vc = DetailViewController()
 
    vc.callBack { (value) in
        self.tf.text = value
    }
    navigationController?.pushViewController(vc, animated: true)
    
}

👆你以学会闭包传值

7.NSUserDefaults传值

UserDefaults为系统单例,通过UserDefaults传值和自定义单例传值原理基本一致。对于UserDefaults传值,首先在获取值的地方,必须保证单例实例key对应的数据确实有值才行,同样的,我们模拟从第一个界面传值到第二个界面

使用步骤:
1.获取UserDefaults单例实例
2.为UserDefaults实例设值
3.同步数据

在第一个界面的该方法中有如下操作

 @objc fileprivate func btnAction()  {
  
    // 获取defaults单例实例
 let defaults = UserDefaults.standard
    //设值
    defaults.setValue(tf.text, forKey: "text")
    // 同步数据
    defaults.synchronize()
    
    let vc = DetailViewController()
 
    navigationController?.pushViewController(vc, animated: true)
    
}

在第二个界面的该方法中有如下操作

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = UIColor.white
    
   let defalts = UserDefaults.standard
    
    guard let text = defalts.object(forKey: "text") as! String? else {
        return
    }
    tfStr = text
    setUpUI()
}

通过UserDefaults传值,不仅仅只局限于传递字符串类型的数据,同时可传递集合类型(字典、数组、集)或者基本数据类型的数据

 

posted on 2021-07-21 19:56  jamiechoo  阅读(503)  评论(0编辑  收藏  举报

导航