คุณแชร์ข้อมูลระหว่างตัวควบคุมมุมมองและวัตถุอื่น ๆ ใน Swift ได้อย่างไร


88

สมมติว่าฉันมีตัวควบคุมมุมมองหลายตัวในแอป Swift ของฉันและฉันต้องการส่งผ่านข้อมูลระหว่างกัน หากฉันมีหลายระดับในสแต็กคอนโทรลเลอร์มุมมองฉันจะส่งข้อมูลไปยังตัวควบคุมมุมมองอื่นได้อย่างไร หรือระหว่างแท็บในตัวควบคุมมุมมองแถบแท็บ?

(โปรดทราบว่าคำถามนี้เป็น "คนสั่น") มีคนถามบ่อยมากจนฉันตัดสินใจเขียนบทช่วยสอนในหัวข้อนี้ ดูคำตอบของฉันด้านล่าง


1
ลองใช้ googling สำหรับผู้ร่วมประชุม
milo526

4
ฉันโพสต์สิ่งนี้เพื่อให้สามารถแก้ปัญหา 10,000 อินสแตนซ์ของคำถามนี้ที่ปรากฏขึ้นทุกวันที่ SO ดูคำตอบของตัวเอง :)
Duncan C

ขออภัยฉันทำปฏิกิริยาเร็วเกินไป :) ดีที่สามารถเชื่อมโยงไปยังสิ่งนี้ได้ :)
milo526

2
ไม่ต้องห่วง. คุณคิดว่าฉันเป็น # 10,001 ใช่ไหม <grin>
Duncan C

4
@DuncanC ฉันไม่ชอบคำตอบของคุณ :( ไม่เป็นไร - เป็นคำตอบที่จับได้ทุกสถานการณ์ ... insomuchas มันจะใช้ได้กับทุกสถานการณ์ แต่ก็ไม่ใช่แนวทางที่ถูกต้องสำหรับเกือบทุกสถานการณ์อย่างไรก็ตามเรื่องนี้ตอนนี้เราได้เข้ามาในหัวของเราแล้ว การทำเครื่องหมายคำถามในหัวข้อว่าซ้ำกับคำถามนี้เป็นความคิดที่ดีหรือไม่กรุณาอย่า
nhgrif

คำตอบ:


91

คำถามของคุณกว้างมาก เพื่อแนะนำว่ามีวิธีแก้ปัญหาง่ายๆเพียงวิธีเดียวสำหรับทุกสถานการณ์คือไร้เดียงสาเล็กน้อย เรามาดูสถานการณ์เหล่านี้กัน


สถานการณ์ที่พบบ่อยที่สุดที่ถามเกี่ยวกับ Stack Overflow จากประสบการณ์ของฉันคือการส่งผ่านข้อมูลอย่างง่ายจากตัวควบคุมมุมมองหนึ่งไปยังตัวควบคุมถัดไป

หากเราใช้สตอรีบอร์ดตัวควบคุมมุมมองแรกของเราสามารถแทนที่prepareForSegueได้ซึ่งเป็นสิ่งที่มีไว้สำหรับ UIStoryboardSegueวัตถุถูกส่งผ่านไปในเมื่อวิธีการนี้เรียกว่าและจะมีการอ้างอิงไปยังตัวควบคุมมุมมองปลายทางของเรา ที่นี่เราสามารถกำหนดค่าที่เราต้องการส่งผ่าน

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

หรือถ้าเราไม่ได้ใช้สตอรี่บอร์ดเราจะโหลดตัวควบคุมมุมมองจากปลายปากกา โค้ดของเรานั้นง่ายกว่าเล็กน้อย

func showNextController() {
    let destination = SecondController(nibName: "SecondController", bundle: nil)
    destination.myInformation = self.myInformation
    show(destination, sender: self)
}

ในทั้งสองกรณีmyInformationเป็นคุณสมบัติบนตัวควบคุมมุมมองแต่ละตัวที่เก็บข้อมูลใด ๆ ที่ต้องการส่งผ่านจากตัวควบคุมมุมมองหนึ่งไปยังตัวควบคุมมุมมองถัดไป เห็นได้ชัดว่าพวกเขาไม่จำเป็นต้องมีชื่อเดียวกันในแต่ละคอนโทรลเลอร์


เราอาจต้องการแบ่งปันข้อมูลระหว่างแท็บในไฟล์UITabBarController.

ในกรณีนี้มันอาจจะง่ายกว่าด้วยซ้ำ

ขั้นแรกให้สร้างคลาสย่อยUITabBarControllerและให้คุณสมบัติสำหรับข้อมูลใด ๆ ที่เราต้องการแชร์ระหว่างแท็บต่างๆ:

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

ตอนนี้ถ้าเรากำลังสร้าง app ของเราจากกระดานที่เราเพียงแค่เปลี่ยนระดับของเราแท็บแถบควบคุมจากเริ่มต้นที่จะ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
}

มีสถานการณ์อื่น ๆ อีกมากมาย คำตอบนี้ไม่ครอบคลุมทั้งหมด


1
ฉันยังเพิ่มว่าบางครั้งคุณต้องการให้ช่องส่งข้อมูลกลับจากตัวควบคุมมุมมองปลายทางไปยังตัวควบคุมมุมมองต้นทาง วิธีทั่วไปในการจัดการกับสถานการณ์นั้นคือการเพิ่มคุณสมบัติผู้รับมอบสิทธิ์ไปยังปลายทางจากนั้นในการจัดเตรียมสำหรับผู้ร่วมประชุมของตัวควบคุมมุมมองต้นทางให้ตั้งค่าคุณสมบัติผู้รับมอบสิทธิ์ของตัวควบคุมมุมมองปลายทางเป็นตนเอง (และกำหนดโปรโตคอลที่กำหนดข้อความที่ VC ปลายทางใช้ในการส่งข้อความไปยัง VC ต้นทาง)
Duncan C

1
nhgrif ฉันเห็นด้วย prepareForSegueที่ปรึกษาให้กับนักพัฒนาใหม่ควรเป็นไปได้ว่าถ้าคุณจำเป็นต้องส่งผ่านข้อมูลระหว่างฉากบนกระดานเรื่องราวการใช้งาน มันแย่เกินไปที่การสังเกตง่ายๆนี้หายไปท่ามกลางคำตอบและการพูดนอกเรื่องอื่น ๆ ที่นี่
Rob

2
@Rob Yup. Singletons และการแจ้งเตือนควรเป็นทางเลือกสุดท้าย เราควรชอบprepareForSegueหรือการถ่ายโอนข้อมูลโดยตรงอื่น ๆในเกือบทุกสถานการณ์จากนั้นก็จะโอเคกับสามเณรเมื่อพวกเขาปรากฏตัวขึ้นพร้อมกับสถานการณ์ที่สถานการณ์เหล่านี้ใช้ไม่ได้ผลจากนั้นเราจะต้องสอนพวกเขาเกี่ยวกับแนวทางระดับโลกเหล่านี้
nhgrif

1
มันขึ้นอยู่กับ. แต่ฉันกังวลมากเกี่ยวกับการใช้ตัวแทนของแอปเป็นฐานการทิ้งรหัสของเราซึ่งเราไม่รู้ว่าจะใส่ที่ไหนอีก นี่คือเส้นทางสู่ความบ้าคลั่ง
nhgrif

2
@nhgrif. ขอบคุณสำหรับคำตอบของคุณ จะเป็นอย่างไรหากคุณต้องการให้ส่งผ่านข้อมูลระหว่างตัวควบคุมมุมมอง 4 หรือ 5 ตัว ถ้าฉันพูด 4-5 viewcontrollers ที่จัดการการเข้าสู่ระบบไคลเอ็นต์และรหัสผ่าน ฯลฯ และฉันต้องการส่งอีเมลของผู้ใช้ระหว่าง viewcontrollers เหล่านี้จะมีวิธีที่สะดวกกว่าการประกาศ var ในแต่ละ viewcontroller จากนั้นส่งผ่านไปยัง prepforsegue มีวิธีใดบ้างที่ฉันสามารถประกาศได้เพียงครั้งเดียวและวิวคอนโทรลเลอร์แต่ละตัวสามารถเข้าถึงได้ แต่จะเป็นการฝึกฝนการเขียนโค้ดที่ดีด้วยวิธีใด
lozflan

45

คำถามนี้ขึ้นมาตลอดเวลา

ข้อเสนอแนะอย่างหนึ่งคือการสร้าง Singleton ที่เก็บข้อมูล: ออบเจ็กต์ที่สร้างขึ้นเพียงครั้งเดียวและครั้งเดียวในชีวิตของแอปพลิเคชันของคุณและจะคงอยู่ตลอดชีวิตของแอป

วิธีนี้เหมาะอย่างยิ่งสำหรับสถานการณ์เมื่อคุณมีข้อมูลแอปทั่วโลกที่จำเป็นต้องพร้อมใช้งาน / แก้ไขได้ในคลาสต่างๆในแอปของคุณ

แนวทางอื่น ๆ เช่นการตั้งค่าการเชื่อมโยงทางเดียวหรือ 2 ทางระหว่างตัวควบคุมมุมมองจะเหมาะกับสถานการณ์ที่คุณส่งข้อมูล / ข้อความโดยตรงระหว่างตัวควบคุมมุมมอง

(ดูคำตอบของ nhgrif ด้านล่างสำหรับทางเลือกอื่น ๆ )

ด้วย Singleton ที่เก็บข้อมูลคุณสามารถเพิ่มคุณสมบัติให้กับคลาสของคุณที่จัดเก็บข้อมูลอ้างอิงไปยังซิงเกิลตันของคุณจากนั้นใช้คุณสมบัตินั้นได้ทุกเมื่อที่คุณต้องการเข้าถึง

คุณสามารถตั้งค่าซิงเกิลตันเพื่อบันทึกเนื้อหาลงในดิสก์เพื่อให้สถานะแอปของคุณคงอยู่ระหว่างการเปิดตัว

ฉันสร้างโครงการสาธิตบน GitHub เพื่อแสดงให้เห็นว่าคุณสามารถทำสิ่งนี้ได้อย่างไร นี่คือลิงค์:

โครงการ SwiftDataContainerSingleton บน GitHub นี่คือ 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

initวิธีการของ DataContainerSingleton เพิ่มผู้สังเกตการณ์สำหรับไฟล์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ในรหัสสังเกตการณ์มันจะช่วยประหยัดคุณสมบัติเก็บข้อมูลที่จะคุณยังสามารถใช้NSCodingCore 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 DefaultsKeys
{
  static let someString  = "someString"
  static let someOtherString  = "someOtherString"
  static let someInt  = "someInt"
}

คุณอ้างอิงค่าคงที่เหล่านี้อย่างใดอย่างหนึ่งดังนี้:

DefaultsKeys.someInt

การใช้ Singleton ที่เก็บข้อมูล:

แอปพลิเคชันตัวอย่างนี้ใช้ประโยชน์จากซิงเกิลตันที่เก็บข้อมูล

มีตัวควบคุมมุมมองสองตัว ครั้งแรกเป็น subclass กำหนดเองของ UIViewController ViewControllerและคนที่สองเป็น subclass กำหนดเองของ SecondVCUIViewController

ตัวควบคุมมุมมองทั้งสองมีช่องข้อความอยู่และทั้งคู่โหลดค่าจากคุณสมบัติของคอนเทนเนอร์ข้อมูล singlelton 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)"
}

รหัสสำหรับบันทึกค่าที่ผู้ใช้แก้ไขกลับไปยังที่เก็บข้อมูลอยู่ในวิธีการของ view controllers textFieldShouldEndEditing:

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

คุณควรโหลดค่าลงในอินเทอร์เฟซผู้ใช้ของคุณใน viewWillAppear แทนที่จะเป็น viewDidLoad เพื่อให้ UI ของคุณอัปเดตทุกครั้งที่ตัวควบคุมมุมมองแสดง


8
ฉันไม่ต้องการลงคะแนนเพราะฉันคิดว่ามันยอดเยี่ยมมากที่คุณลงทุนเวลาในการสร้างคำถามและคำตอบเป็นทรัพยากร ขอขอบคุณ. อย่างไรก็ตามฉันคิดว่าเราสร้างความเสียหายอย่างมากให้กับนักพัฒนาหน้าใหม่เพื่อสนับสนุน singletons สำหรับโมเดลวัตถุ ฉันไม่ได้อยู่ในค่าย "singletons are evil" (แม้ว่า noobs ควรใช้คำว่า google เพื่อให้เข้าใจปัญหาได้ดีขึ้น) แต่ฉันคิดว่าข้อมูลโมเดลเป็นการใช้ singletons ที่น่าสงสัย / เป็นที่ถกเถียงกัน
Rob

ชอบที่จะเห็นการเขียนที่ยอดเยี่ยมเป็นของคุณเกี่ยวกับลิงก์ 2 ทาง
Cmag

@Duncan C สวัสดี Duncan ฉันกำลังสร้างวัตถุคงที่ในแต่ละรุ่นดังนั้นฉันจึงได้รับข้อมูลจากที่ใดก็ตามที่เป็นแนวทางที่ถูกต้องหรือฉันต้องทำตามเส้นทางของคุณเพราะดูเหมือนว่าถูกต้องมาก
Virendra Singh Rathore

@VirendraSinghRathore ตัวแปรคงที่ทั่วโลกเป็นวิธีที่แย่ที่สุดในการแชร์ข้อมูลระหว่างแอป พวกเขาจับคู่ส่วนต่างๆของแอปของคุณเข้าด้วยกันอย่างแน่นหนาและแนะนำการพึ่งพาระหว่างกัน มันตรงข้ามกับ "ขวามาก"
Duncan C

@DuncanC - รูปแบบนี้จะใช้ได้กับออบเจ็กต์ CurrentUser หรือไม่โดยทั่วไปคือผู้ใช้รายเดียวที่ลงชื่อเข้าใช้แอป thx
timpone

9

สวิฟต์ 4

มีหลายวิธีในการส่งผ่านข้อมูลอย่างรวดเร็ว ฉันกำลังเพิ่มแนวทางที่ดีที่สุดบางส่วน

1) การใช้ StoryBoard Segue

Storyboard segues มีประโยชน์อย่างมากในการส่งผ่านข้อมูลระหว่าง Source และ Destination View Controllers และในทางกลับกันด้วย

// 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
        }

    }

สำหรับชาว Google ที่กำลังเป็นอย่างเต็มที่โดยสิ้นเชิงและหายไปเป็นไปได้ที่จะนำ StackOverflow ตอบสวิฟท์โค้ดที่ผมเป็นดูเหมือนว่าสันนิษฐานว่าคุณควรทราบว่าพวกเขาสรุปรหัสไป: ผมใช้ตัวเลือกที่ 1) ที่จะส่งจากไปViewControllerA ViewControllerBฉันเพิ่งติดข้อมูลโค้ดที่ด้านล่างของของฉันViewControllerA.swift( ViewControllerA.swiftแน่นอนว่าไฟล์ของคุณชื่ออะไร) ก่อนวงเล็บปีกกาสุดท้าย " prepare" เป็นฟังก์ชันพิเศษในตัวที่มีอยู่แล้วในคลาสที่กำหนด [ที่ไม่ทำอะไรเลย] ซึ่งเป็นสาเหตุที่คุณต้อง " override" มัน
velkoon

8

อีกทางเลือกหนึ่งคือการใช้ศูนย์การแจ้งเตือน (NSNotificationCenter) และโพสต์การแจ้งเตือน นั่นคือการมีเพศสัมพันธ์ที่หลวมมาก ผู้ส่งการแจ้งเตือนไม่จำเป็นต้องรู้หรือสนใจว่าใครกำลังฟังอยู่ เพียงแค่โพสต์การแจ้งเตือนและลืมมันไป

การแจ้งเตือนเป็นสิ่งที่ดีสำหรับการส่งข้อความแบบหนึ่งต่อหลายข้อความเนื่องจากอาจมีผู้สังเกตการณ์จำนวนหนึ่งที่รับฟังข้อความที่ระบุ


3
โปรดทราบว่าการใช้ศูนย์การแจ้งเตือนจะแนะนำการเชื่อมต่อที่อาจหลวมเกินไป อาจทำให้การติดตามขั้นตอนของโปรแกรมของคุณเป็นเรื่องยากมากดังนั้นจึงควรใช้ด้วยความระมัดระวัง
Duncan C

2

แทนที่จะสร้างตัวควบคุมข้อมูลเดี่ยวฉันขอแนะนำให้สร้างอินสแตนซ์ตัวควบคุมข้อมูลและส่งต่อไปรอบ ๆ เพื่อรองรับการฉีดขึ้นต่อกันฉันจะสร้างDataControllerโปรโตคอลก่อน:

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

จากนั้นฉันจะสร้างSpecificDataControllerคลาส (หรือชื่ออะไรก็ได้ที่เหมาะสม):

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

ระดับแล้วควรจะมีข้อมูลที่จะถือเป็นViewController dataControllerแจ้งให้ทราบว่าประเภทของการเป็นโปรโตคอลdataController DataControllerวิธีนี้ง่ายต่อการปิดการใช้งานตัวควบคุมข้อมูล:

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

ในAppDelegateเราสามารถตั้งค่า viewController ของdataController:

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

เมื่อเราย้ายไปยัง viewController อื่นเราสามารถส่งdataControllerต่อใน:

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

ตอนนี้เมื่อเราต้องการเปลี่ยนตัวควบคุมข้อมูลสำหรับงานอื่นเราสามารถทำได้ในไฟล์ AppDelegateและไม่ต้องเปลี่ยนรหัสอื่นใดที่ใช้ตัวควบคุมข้อมูล

แน่นอนว่านี่เป็นเรื่องที่เกินความจำเป็นหากเราเพียงแค่ต้องการส่งผ่านค่าเดียว ในกรณีนี้ควรใช้คำตอบของ nhgrif

ด้วยวิธีนี้เราสามารถแยกมุมมองจากส่วนตรรกะได้


1
สวัสดีวิธีนี้สะอาดทดสอบได้และสิ่งที่ฉันใช้เวลาส่วนใหญ่ในแอปพลิเคชันขนาดเล็ก แต่ในแอปพลิเคชันขนาดใหญ่ซึ่งไม่ใช่ทุก VC (อาจไม่ใช่ VC ราก) อาจต้องการการพึ่งพา (เช่น DataController ในกรณีนี้) ดูเหมือนว่าจะสิ้นเปลืองสำหรับ VC ทุกคนที่ต้องการการพึ่งพาเพียงเพื่อที่จะผ่านมันไป นอกจากนี้หากคุณใช้ VC ประเภทต่างๆ (เช่น UIVC ปกติเทียบกับ NavigationVC) คุณจะต้องซับคลาสประเภทต่างๆเหล่านั้นเพียงเพื่อเพิ่มตัวแปรการอ้างอิงนั้น คุณเข้าใกล้สิ่งนี้ได้อย่างไร?
RobertoCuba

1

ดังที่ @nhgrif ชี้ให้เห็นในคำตอบที่ยอดเยี่ยมของเขามีหลายวิธีที่ VC (ตัวควบคุมมุมมอง) และวัตถุอื่น ๆ สามารถสื่อสารกันได้

ข้อมูลเดี่ยวที่ฉันระบุไว้ในคำตอบแรกของฉันคือจริงๆแล้วเกี่ยวกับการแบ่งปันและการประหยัดสภาวะโลกมากกว่าการสื่อสารโดยตรง

คำตอบของ nhrif ช่วยให้คุณส่งข้อมูลโดยตรงจากต้นทางไปยัง VC ปลายทาง ดังที่ฉันได้กล่าวไว้ในการตอบกลับคุณสามารถส่งข้อความกลับจากปลายทางไปยังต้นทางได้เช่นกัน

ในความเป็นจริงคุณสามารถตั้งค่าช่องสัญญาณทางเดียวหรือ 2 ทางที่ใช้งานอยู่ระหว่างตัวควบคุมมุมมองต่างๆ หากตัวควบคุมมุมมองถูกเชื่อมโยงผ่านทางกระดานเรื่องราวเวลาในการตั้งค่าลิงก์จะอยู่ในวิธีการจัดเตรียมสำหรับการ Segue

ฉันมีโปรเจ็กต์ตัวอย่างบน Github ที่ใช้พาเรนต์วิวคอนโทรลเลอร์เพื่อโฮสต์ 2 มุมมองตารางที่แตกต่างกันเป็นลูก ตัวควบคุมมุมมองชายด์ถูกเชื่อมโยงโดยใช้เซ็กส์แบบฝังและตัวควบคุมมุมมองพาเรนต์จะเชื่อมโยงการเชื่อมโยงแบบ 2 ทางกับคอนโทรลเลอร์มุมมองแต่ละตัวในวิธีการจัดเตรียม

คุณสามารถค้นหาโปรเจ็กต์นั้นได้ใน github (ลิงค์) อย่างไรก็ตามฉันเขียนไว้ใน Objective-C และยังไม่ได้แปลงเป็น Swift ดังนั้นหากคุณไม่สะดวกใน Objective-C อาจเป็นเรื่องยากที่จะปฏิบัติตาม


1

SWIFT 3:

หากคุณมีสตอรี่บอร์ดที่ระบุการแบ่งส่วนให้ใช้:

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

แม้ว่าคุณจะทำทุกอย่างโดยใช้โปรแกรมรวมถึงการนำทางระหว่าง UIViewControllers ที่แตกต่างกันให้ใช้วิธีการ:

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

หมายเหตุ: ในการใช้วิธีที่สองที่คุณต้องสร้าง UINavigationController คุณกำลังกด UIViewControllers บนผู้รับมอบสิทธิ์และจำเป็นต้องเป็นไปตามโปรโตคอล 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

    }
}

never do self.delegate = self
malhal

1

ขึ้นอยู่กับว่าคุณต้องการรับข้อมูลเมื่อใด

หากคุณต้องการรับข้อมูลเมื่อใดก็ตามที่คุณต้องการสามารถใช้รูปแบบซิงเกิลตัน คลาสรูปแบบแอ็คทีฟระหว่างรันไทม์ของแอพ นี่คือตัวอย่างของรูปแบบซิงเกิลตัน

class AppSession: NSObject {

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

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

        print(AppSession.shared.username)
    }
}

หากคุณต้องการรับข้อมูลหลังจากดำเนินการใด ๆ สามารถใช้ 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")
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.