developer tip

Swift에서 뷰 컨트롤러와 다른 객체간에 데이터를 어떻게 공유합니까?

optionbox 2020. 9. 14. 08:20
반응형

Swift에서 뷰 컨트롤러와 다른 객체간에 데이터를 어떻게 공유합니까?


Swift 앱에 여러 개의 뷰 컨트롤러가 있고 이들간에 데이터를 전달할 수 있기를 원한다고 가정 해 보겠습니다. 뷰 컨트롤러 스택에서 여러 수준 아래에있는 경우 데이터를 다른 뷰 컨트롤러로 어떻게 전달합니까? 아니면 탭 모음보기 컨트롤러의 탭간에?

(참고,이 질문은 "벨소리"입니다.) 너무 많은 질문을 받아 주제에 대한 자습서를 작성하기로 결정했습니다. 아래 내 대답을 참조하십시오.


귀하의 질문은 매우 광범위합니다. 모든 시나리오에 대한 하나의 간단한 포괄 솔루션이 있다고 제안하는 것은 약간 순진합니다. 따라서 이러한 시나리오 중 일부를 살펴 보겠습니다.


내 경험상 Stack Overflow에서 가장 일반적인 시나리오는 한 뷰 컨트롤러에서 다음 뷰 컨트롤러로 정보를 전달하는 것입니다.

스토리 보드를 사용하는 경우 첫 번째 뷰 컨트롤러는을 재정의 할 수 있습니다 prepareForSegue. UIStoryboardSegue개체는이 메소드를 호출 할 때 전달하고, 우리 목적지보기 컨트롤러에 대한 참조를 포함한다. 여기에서 전달하려는 값을 설정할 수 있습니다.

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "MySegueID" {
        if let destination = segue.destinationViewController as? SecondController {
            destination.myInformation = self.myInformation
        }
    }
}

또는 스토리 보드를 사용하지 않는 경우 펜촉에서 뷰 컨트롤러를로드합니다. 그러면 코드가 약간 더 간단합니다.

func showNextController() {
    let destination = SecondController(nibName: "SecondController", bundle: NSBundle.mainBundle())
    destination.myInformation = self.myInformation
    self.showViewController(destination, sender: self)
}

두 경우 모두 myInformation는 한보기 컨트롤러에서 다음보기 컨트롤러로 전달해야하는 데이터를 보유하는 각보기 컨트롤러의 속성입니다. 분명히 각 컨트롤러에서 같은 이름을 가질 필요는 없습니다.


.NET Framework의 탭간에 정보를 공유 할 수도 있습니다 UITabBarController.

이 경우 실제로 잠재적으로 더 간단합니다.

먼저의 하위 클래스를 만들고 UITabBarController다양한 탭간에 공유하려는 정보에 대한 속성을 지정합니다.

class MyCustomTabController: UITabBarController {
    var myInformation: [String: AnyObject]?
}

이제 스토리 보드에서 앱을 빌드하는 경우 탭 막대 컨트롤러의 클래스를 기본값 UITabBarController에서 MyCustomTabController. 스토리 보드를 사용하지 않는 경우 기본 UITabBarController클래스가 아닌이 사용자 지정 클래스의 인스턴스를 인스턴스화 하고 여기에 뷰 컨트롤러를 추가하면됩니다.

이제 탭 막대 컨트롤러 내의 모든 뷰 컨트롤러는 다음과 같이이 속성에 액세스 할 수 있습니다.

if let tbc = self.tabBarController as? MyCustomTabController {
    // do something with tbc.myInformation
}

그리고 UINavigationController같은 방식 으로 서브 클래 싱 함으로써 전체 탐색 스택에서 데이터를 공유하는 동일한 접근 방식을 취할 수 있습니다.

if let nc = self.navigationController as? MyCustomNavController {
    // do something with nc.myInformation
}

몇 가지 다른 시나리오가 있습니다. 이 답변이 모든 것을 포함하지는 않습니다.


이 질문은 항상 떠 오릅니다.

한 가지 제안은 데이터 컨테이너 싱글 톤을 만드는 것입니다. 애플리케이션 수명에서 한 번만 생성되고 앱 수명 동안 지속되는 개체입니다.

이 접근 방식은 앱의 여러 클래스에서 사용 / 수정해야하는 글로벌 앱 데이터가있는 상황에 적합합니다.

뷰 컨트롤러간에 단방향 또는 양방향 링크를 설정하는 것과 같은 다른 접근 방식은 뷰 컨트롤러간에 직접 정보 / 메시지를 전달하는 상황에 더 적합합니다.

(다른 대안은 아래 nhgrif의 답변을 참조하십시오.)

데이터 컨테이너 싱글 톤을 사용하면 싱글 톤에 대한 참조를 저장하는 속성을 클래스에 추가 한 다음 액세스가 필요할 때마다 해당 속성을 사용합니다.

싱글 톤을 설정하여 콘텐츠를 디스크에 저장하여 실행간에 앱 상태가 유지되도록 할 수 있습니다.

이 작업을 수행하는 방법을 보여주는 GitHub에서 데모 프로젝트를 만들었습니다. 여기 링크가 있습니다:

GitHub의 SwiftDataContainerSingleton 프로젝트 다음은 해당 프로젝트의 README입니다.

SwiftDataContainerSingleton

데이터 컨테이너 싱글 톤을 사용하여 애플리케이션 상태를 저장하고 객체간에 공유하는 데모입니다.

DataContainerSingleton클래스는 실제 싱글이다.

정적 상수 sharedDataContainer사용 하여 싱글 톤에 대한 참조를 저장합니다.

싱글 톤에 액세스하려면 구문을 사용하십시오.

DataContainerSingleton.sharedDataContainer

샘플 프로젝트는 데이터 컨테이너에 3 개의 속성을 정의합니다.

  var someString: String?
  var someOtherString: String?
  var someInt: Int?

someInt데이터 컨테이너에서 속성 을로드하려면 다음과 같은 코드를 사용합니다.

let theInt = DataContainerSingleton.sharedDataContainer.someInt

someInt에 값을 저장하려면 다음 구문을 사용합니다.

DataContainerSingleton.sharedDataContainer.someInt = 3

DataContainerSingleton의 init메서드는 UIApplicationDidEnterBackgroundNotification. 해당 코드는 다음과 같습니다.

goToBackgroundObserver = NSNotificationCenter.defaultCenter().addObserverForName(
  UIApplicationDidEnterBackgroundNotification,
  object: nil,
  queue: nil)
  {
    (note: NSNotification!) -> Void in
    let defaults = NSUserDefaults.standardUserDefaults()
    //-----------------------------------------------------------------------------
    //This code saves the singleton's properties to NSUserDefaults.
    //edit this code to save your custom properties
    defaults.setObject( self.someString, forKey: DefaultsKeys.someString)
    defaults.setObject( self.someOtherString, forKey: DefaultsKeys.someOtherString)
    defaults.setObject( self.someInt, forKey: DefaultsKeys.someInt)
    //-----------------------------------------------------------------------------

    //Tell NSUserDefaults to save to disk now.
    defaults.synchronize()
}

관찰자 코드에서 데이터 컨테이너의 속성을 NSUserDefaults. NSCoding상태 데이터를 저장하기 위해, Core Data 또는 기타 다양한 방법을 사용할 수도 있습니다 .

DataContainerSingleton의 init메서드는 또한 속성에 대해 저장된 값을로드하려고합니다.

init 메소드의 해당 부분은 다음과 같습니다.

let defaults = NSUserDefaults.standardUserDefaults()
//-----------------------------------------------------------------------------
//This code reads the singleton's properties from NSUserDefaults.
//edit this code to load your custom properties
someString = defaults.objectForKey(DefaultsKeys.someString) as! String?
someOtherString = defaults.objectForKey(DefaultsKeys.someOtherString) as! String?
someInt = defaults.objectForKey(DefaultsKeys.someInt) as! Int?
//-----------------------------------------------------------------------------

NSUserDefaults에 값을로드하고 저장하기위한 키는 DefaultsKeys다음과 같이 정의 된 struct의 일부인 문자열 상수로 저장됩니다 .

struct DefaultsKeys
{
  static let someString  = "someString"
  static let someOtherString  = "someOtherString"
  static let someInt  = "someInt"
}

다음과 같이 이러한 상수 중 하나를 참조합니다.

DefaultsKeys.someInt

데이터 컨테이너 싱글 톤 사용 :

이 샘플 애플리케이션은 데이터 컨테이너 싱글 톤을 세 번 사용합니다.

두 개의 뷰 컨트롤러가 있습니다. 첫 번째는 UIViewController의 사용자 정의 하위 클래스이고 ViewController두 번째는 UIViewController의 사용자 정의 하위 클래스입니다 SecondVC.

두 뷰 컨트롤러에는 모두 텍스트 필드가 있으며 둘 다 데이터 컨테이너 싱글 톤의 someInt속성에서 viewWillAppear메서드 의 텍스트 필드로 값을로드하고 둘 다 텍스트 필드 의 현재 값을 데이터 컨테이너의 'someInt'에 다시 저장합니다.

값을 텍스트 필드에로드하는 코드는 viewWillAppear:메소드에 있습니다.

override func viewWillAppear(animated: Bool)
{
  //Load the value "someInt" from our shared ata container singleton
  let value = DataContainerSingleton.sharedDataContainer.someInt ?? 0

  //Install the value into the text field.
  textField.text =  "\(value)"
}

사용자가 편집 한 값을 데이터 컨테이너에 다시 저장하는 코드는 뷰 컨트롤러의 textFieldShouldEndEditing메서드에 있습니다.

 func textFieldShouldEndEditing(textField: UITextField) -> Bool
 {
   //Save the changed value back to our data container singleton
   DataContainerSingleton.sharedDataContainer.someInt = textField.text!.toInt()
   return true
 }

뷰 컨트롤러가 표시 될 때마다 UI가 업데이트되도록 viewDidLoad가 아닌 viewWillAppear의 사용자 인터페이스에 값을로드해야합니다.


스위프트 4

신속하게 데이터를 전달하기위한 접근 방식이 너무 많습니다. 여기에 최선의 접근 방식을 추가하고 있습니다.

1) StoryBoard Segue 사용

Storyboard segue는 소스와 대상 뷰 컨트롤러간에 데이터를 전달하는 데 매우 유용하며 그 반대의 경우도 마찬가지입니다.

// If you want to pass data from ViewControllerB to ViewControllerA while user tap on back button of ViewControllerB.
        @IBAction func unWindSeague (_ sender : UIStoryboardSegue) {
            if sender.source is ViewControllerB  {
                if let _ = sender.source as? ViewControllerB {
                    self.textLabel.text = "Came from B = B->A , B exited"
                }
            }
        }

// If you want to send data from ViewControllerA to ViewControllerB
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if  segue.destination is ViewControllerB {
                if let vc = segue.destination as? ViewControllerB {
                    vc.dataStr = "Comming from A View Controller"
                }
            }
        }

2) 위임 방법 사용

ViewControllerD

//Make the Delegate protocol in Child View Controller (Make the protocol in Class from You want to Send Data)
    protocol  SendDataFromDelegate {
        func sendData(data : String)
    }

    import UIKit

    class ViewControllerD: UIViewController {

        @IBOutlet weak var textLabelD: UILabel!

        var delegate : SendDataFromDelegate?  //Create Delegate Variable for Registering it to pass the data

        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            textLabelD.text = "Child View Controller"
        }

        @IBAction func btnDismissTapped (_ sender : UIButton) {
            textLabelD.text = "Data Sent Successfully to View Controller C using Delegate Approach"
            self.delegate?.sendData(data:textLabelD.text! )
            _ = self.dismiss(animated: true, completion:nil)
        }
    }

ViewControllerC

    import UIKit

    class ViewControllerC: UIViewController , SendDataFromDelegate {

        @IBOutlet weak var textLabelC: UILabel!

        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
        }

        @IBAction func btnPushToViewControllerDTapped( _ sender : UIButton) {
            if let vcD = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerD") as?  ViewControllerD  {
                vcD.delegate = self // Registring Delegate (When View Conteoller D gets Dismiss It can call sendData method
    //            vcD.textLabelD.text = "This is Data Passing by Referenceing View Controller D Text Label." //Data Passing Between View Controllers using Data Passing
                self.present(vcD, animated: true, completion: nil)
            }
        }

        //This Method will called when when viewcontrollerD will dismiss. (You can also say it is a implementation of Protocol Method)
        func sendData(data: String) {
            self.textLabelC.text = data
        }

    }

또 다른 대안은 알림 센터 (NSNotificationCenter)를 사용하고 알림을 게시하는 것입니다. 그것은 매우 느슨한 결합입니다. 알림을 보낸 사람은 누가 듣고 있는지 알거나 신경 쓸 필요가 없습니다. 알림을 게시하고 잊어 버립니다.

알림은 주어진 메시지를 수신하는 관찰자가 임의의 숫자 일 수 있으므로 일대 다 메시지 전달에 유용합니다.


SWIFT 3 :

식별 된 segues가있는 스토리 보드가있는 경우

func prepare(for segue: UIStoryboardSegue, sender: Any?)

다른 UIViewController 간의 탐색을 포함하여 프로그래밍 방식으로 모든 작업을 수행하는 경우 메서드를 사용합니다.

func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)

Note: to use the second way you need to make your UINavigationController, you are pushing UIViewControllers on, a delegate and it needs to conform to the protocol UINavigationControllerDelegate:

   class MyNavigationController: UINavigationController, UINavigationControllerDelegate {

    override func viewDidLoad() {
        self.delegate = self
    }

    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {

     // do what ever you need before going to the next UIViewController or back
     //this method will be always called when you are pushing or popping the ViewController

    }
}

Instead of creating a data controller singelton I would suggest to create a data controller instance and pass it around. To support dependency injection I would first create a DataController protocol:

protocol DataController {
    var someInt : Int {get set} 
    var someString : String {get set}
}

Then I would create a SpecificDataController (or whatever name would currently be appropriate) class:

class SpecificDataController : DataController {
   var someInt : Int = 5
   var someString : String = "Hello data" 
}

The ViewController class should then have a field to hold the dataController. Notice that the type of dataController is the protocol DataController. This way it's easy to switch out data controller implementations:

class ViewController : UIViewController {
   var dataController : DataController?
   ...
}

In AppDelegate we can set the viewController's dataController:

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    if let viewController = self.window?.rootViewController as? ViewController {
        viewController.dataController =  SpecificDataController()
    }   
    return true
}

When we move to a different viewController we can pass the dataController on in:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    ...   
}

Now when we wish to switch out the data controller for a different task we can do this in the AppDelegate and do not have to change any other code that uses the data controller.

This is of course overkill if we simply want to pass around a single value. In this case it's best to go with nhgrif's answer.

With this approach we can separate view form the logic part.


As @nhgrif pointed out in his excellent answer, there are lots of different ways that VCs (view controllers) and other objects can communicate with each other.

The data singleton I outlined in my first answer is really more about sharing and saving global state than about communicating directly.

nhrif's answer lets you send information directly from the source to the destination VC. As I mentioned in reply, it's also possible to send messages back from the destination to the source.

In fact, you can set up an active one-way or 2-way channel between different view controllers. If the view controllers are linked via a storyboard segue, the time to set up the links is in the prepareFor Segue method.

I have a sample project on Github that uses a parent view controller to host 2 different table views as children. The child view controllers are linked using embed segues, and the parent view controller wires up 2-way links with each view controller in the prepareForSegue method.

You can find that project on github (link). I wrote it in Objective-C, however, and haven't converted it to Swift, so if you're not comfortable in Objective-C it might be a little hard to follow


It depends when you want to get data.

If you want to get data whenever you want, can use a singleton pattern. The pattern class is active during the app runtime. Here is an example of the singleton pattern.

class AppSession: NSObject {

    static let shared = SessionManager()
    var username = "Duncan"
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        print(AppSession.shared.username)
    }
}

If you want to get data after any action, can use NotificationCenter.

extension Notification.Name {
    static let loggedOut = Notification.Name("loggedOut")
}

@IBAction func logoutAction(_ sender: Any) {
    NotificationCenter.default.post(name: .loggedOut, object: nil)
}

NotificationCenter.default.addObserver(forName: .loggedOut, object: nil, queue: OperationQueue.main) { (notify) in
    print("User logged out")
}

참고URL : https://stackoverflow.com/questions/29734954/how-do-you-share-data-between-view-controllers-and-other-objects-in-swift

반응형