Daten von MainViewController zweiten ViewController bereitstellen

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

Rabattcode für die heise MacDev 2020: Macoun20

Aufgrund der Corona-Krise: Die Veröffentlichung von Stellenangeboten und -gesuchen ist bis 31.12.2020 kostenfrei. Das beinhaltet auch Angebote und Gesuche von und für Freischaffende und Selbstständige.

  • Daten von MainViewController zweiten ViewController bereitstellen

    Hallo zusammen,

    ich habe ein Verständnis Problem zum Thema Datenaustausch zwischen zwei ViewController.
    Ich habe versucht mich bei Dr. Google schlau zu machen, habe auch einiges gelesen (Properties, Segues, Notification Center), probiert aber ich komme irgendwie nicht weiter und mir fehlt das prinzipielle Verständnis wie sich so etwas einfach realisieren lässt.
    Hier kurz zur Erläuterung was ich machen möchte.
    Ich habe eine App mit einem MainViewController. Hier werden Daten mittels einen API aus dem Internet geladen, verarbeitet und in Labels dargestellt.Dann wollte ich ein SideMenu in dem ich dann einen zweiten, InfoViewController, habe der zusätzliche Informationen darstellen soll. Einige der Infos die ich darstellen möchte wurden im MainViewController über die API geladen bzw. errechnet.

    Ich kenne das von Java, in der man über eine s.g. Model Class mit getter und setter Methoden die Daten speichert (set-) und dann wieder abholt (get-).

    Nun fehlt mir die Idee wie ich das in Swift realisieren kann. Alles was ich bis jetzt gelesen habe erscheint mir sehr komplex (z.B. Notification Center) für das was ich benötige.

    Kann mir jemand an Hand eines kurzen Beispiel erläutern wie Informationen von Controller A für Controller B bereitstellen kann?

    Gruß

    Ralf

    Hier noch ein bisschen Code um die Frage vielleicht ein bisschen klarer zu machen. Das mit dem SideMenu habe ich mir aus Beiträgen von StackOverflow und Tutorials "zusammen gebaut", es ist also durchaus möglich das hier einiges nicht wirklich klug ist .. bin daher um jeden Ratschlag dankbar ... auch wenn es letztendlich endet in: Vergiss es :D

    C-Quellcode

    1. class ViewController: UIViewController, MenuControllerDelegate {
    2. private var sideMenu: SideMenuNavigationController?
    3. private let aboutController = AboutViewController()
    4. private let infoController = InfoViewController()
    5. ...
    6. ...
    7. func loadData() {
    8. ...
    9. Hier werden die Daten vom API geholt, verarbeitet und in Labels auf dem ViewController dargestellt ...
    10. ...
    11. }
    12. override func viewDidLoad() {
    13. super.viewDidLoad()
    14. self.view.backgroundColor = UIColor(patternImage: UIImage(named: "SandSeamless.jpg")!)
    15. // Set label font
    16. queryDateActual.font = UIFont(name:"AppleSDGothicNeo-SemiBold", size: 25)
    17. investDateEnd.font = UIFont(name:"AppleSDGothicNeo-SemiBold", size: 25)
    18. showProfitLossEnd.font = UIFont(name:"AppleSDGothicNeo-SemiBold", size: 40)
    19. showProfitLossActual.font = UIFont(name:"AppleSDGothicNeo-SemiBold", size: 40)
    20. // Set showProfitLostEnd label layout
    21. showProfitLossEnd.textColor = UIColor.white
    22. showProfitLossEnd.layer.cornerRadius = 25
    23. showProfitLossEnd.layer.masksToBounds = true
    24. // Set showProfitLostActual label layout
    25. showProfitLossActual.textColor = UIColor.white
    26. showProfitLossActual.layer.cornerRadius = 25
    27. showProfitLossActual.layer.masksToBounds = true
    28. let menu = MenuController(with: SideMenuItem.allCases)
    29. menu.delegate = self
    30. sideMenu = SideMenuNavigationController(rootViewController: menu)
    31. sideMenu?.leftSide = true
    32. sideMenu?.setNavigationBarHidden(true, animated: false)
    33. SideMenuManager.default.leftMenuNavigationController = sideMenu
    34. SideMenuManager.default.addPanGestureToPresent(toView: view)
    35. addChildControllers()
    36. // load Data
    37. loadData()
    38. }
    39. private func addChildControllers() {
    40. addChild(aboutController)
    41. addChild(infoController)
    42. view.addSubview(aboutController.view)
    43. view.addSubview(infoController.view)
    44. aboutController.view.frame = view.bounds
    45. infoController.view.frame = view.bounds
    46. aboutController.didMove(toParent: self)
    47. infoController.didMove(toParent: self)
    48. aboutController.view.isHidden = true
    49. infoController.view.isHidden = true
    50. }
    51. @IBAction func didTapMenuButton() {
    52. present(sideMenu!, animated: true)
    53. }
    54. func didSelectMenuItem(named: SideMenuItem) {
    55. sideMenu?.dismiss(animated: true, completion: nil)
    56. // Don't show Name of Navigation Item (Home, About ..., etc.) in the Navigation Bar as
    57. // Title. Uncomment the next line to show the Title.
    58. //title = named.rawValue
    59. switch named {
    60. case .home:
    61. aboutController.view.isHidden = true
    62. infoController.view.isHidden = true
    63. case .info:
    64. aboutController.view.isHidden = true
    65. infoController.view.isHidden = false
    66. case .about:
    67. aboutController.view.isHidden = false
    68. infoController.view.isHidden = true
    69. }
    70. }
    71. }
    Alles anzeigen


    C-Quellcode

    1. class InfoViewController: UIViewController {
    2. /// The text property for "Passing data forward with properties"
    3. var text:String = ""
    4. override func viewDidLoad() {
    5. super.viewDidLoad()
    6. //view.backgroundColor = .red
    7. view.backgroundColor = UIColor(patternImage: UIImage(named: "SandSeamless.jpg")!)
    8. print(text)
    9. }
    10. }
    Alles anzeigen
    In dem InfoViewController habe ich dann versucht über die Property text "Daten auszutauschen". Im ViewController habe ich dann folgendes versucht:


    Quellcode

    1. infoController.text = "Daten von ViewController zur Darstellung in InfoController"


    Allerdings, egal wo in ViewController ich den Code rein schreibe wird dieser beim Aufruf des InfoViewController nicht auf der Konsole ausgegeben :?: ?(

    Vermutlich ganz falscher Ansatz ...

    Update:

    Ich habe gerade gemerkt das der InfoViewController schon erstellt wird bevor ich die Property rein gebe und somit natürlich nicht die Ausgabe gemacht wird da der print(text) schon mit dem Default String aus InfoViewController (var text:String = "") gemacht wird.

    Dann wird es vermutlich doch irgendwie auf Observer hinauslaufen .. ?? ... ??

    Gruß

    Ralf

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von ralfb ()

  • Ich habe jetzt nicht Deinen Code nachvollzogen, aber ein mögliches Vorgehen wäre wie folgt:
    1. Der Detail-VC "B" hat ein Property zum Aufnehmen der Daten, z. B. ein Dictionary
    2. Der Main-VC "A" wechselt mit einer Segue nach B und setzt in "prepareForSegue" dieses Property anhand des Destination-VC (nicht neu instanzieren).
    3. B nutzt dann z. B. im viewDidLoad das Property zum Setzen von IBOutlets
    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Hallo @MyMattes

    erst einmal Danke für Deine Antwort.
    Ich habe gestern Abend, vor deiner Antwort, noch ein wenig über Notification Center gelesen, weil mir das irgendwie logisch klang um mein Problem zu lösen.

    Ich habe dann folgendes implementiert und es scheint auch zu funktionieren. Hier mal meine Code Auszüge.

    Detail-VC "B"

    Quellcode

    1. // The notification name you want to observe and post
    2. static let notificationName = Notification.Name("myNotificationName")
    3. // This function is invoked when the notification is received and
    4. // set the userInfo to the label(s)/Views
    5. @objc func onNotification(notification:Notification)
    6. {
    7. // `userInfo` contains the data you sent along with the notification
    8. print(notification.userInfo)
    9. if let queryDateActual = notification.userInfo?["queryDateActual"] as? String {
    10. textLabel.text? = String(queryDateActual)
    11. }
    12. }
    Alles anzeigen


    Main-VC "A" -- Nachdem die Daten ermittelt wurden, sprich in der loadData() Methode Notification posten

    Quellcode

    1. NotificationCenter.default.post(name: SecondaryControllerView.notificationName, object: nil, userInfo:["queryDateActual": dateFormattedString, "isImportant": true])

    Ich werde deine Möglichkeit aber auf jeden fall auch noch testen.

    Frage:

    Siehst Du einen "drawback" bei meiner genutzten Methode oder wäre das aus Swift Sicht eine akzeptable Methode um das zu realisieren?

    Gruß

    Ralf
  • Hallo @MyMattes,

    ich habe gerade mal versucht das mit dem Segue einzubauen, aber leider funktioniert das nicht so wie ich es gedacht habe, bzw. gedacht hätte es verstanden zu haben.
    Mir fehlt vermutlich die Stelle wo der Segue aufgerufen wird bzw. werden sollte. Ich vermute es liegt daran wie ich den SecondaryViewController bei mir erstelle bzw. starte.

    Folgendes habe ich bis jetzt eingebaut:

    MainViewController - der die Daten "abgibt"

    Quellcode

    1. override func prepare(for segue: UIStoryboardSegue, sender: Any?)
    2. {
    3. // Determine what the segue destination is
    4. if segue.destination is SecondaryControllerView
    5. {
    6. let vc = segue.destination as? SecondaryControllerView
    7. vc?.username = "Ralf"
    8. }
    9. }


    SecondaryControllerView - der die Daten empfängt und darstellen soll

    Quellcode

    1. var username:String = "Default Text"
    2. @IBOutlet weak var textLabelSegue: UILabel!
    3. override func viewDidLoad() {
    4. super.viewDidLoad()
    5. // Observe for the notification, and define the function that's called when the notification is received
    6. NotificationCenter.default.addObserver(self, selector: #selector(onNotification(notification:)), name: SecondaryControllerView.notificationName, object: nil)
    7. view.backgroundColor = .green
    8. textLabelSegue?.text = username
    9. }
    Alles anzeigen
    Wenn ich die App starte und dann auf den Secondary View wechsle, was ja über das SideMenu passiert, dann wird in dem Label nur der Defaults text dargestellt.
    Der Secondary View wird bei mir folgendermaßen imitiert und dargestellt:

    Quellcode

    1. private func addChildControllers() {
    2. addChild(aboutController)
    3. addChild(infoController)
    4. addChild(secondaryController)
    5. view.addSubview(aboutController.view)
    6. view.addSubview(infoController.view)
    7. view.addSubview(secondaryController.view)
    8. aboutController.view.frame = view.bounds
    9. infoController.view.frame = view.bounds
    10. secondaryController.view.frame = view.bounds
    11. aboutController.didMove(toParent: self)
    12. infoController.didMove(toParent: self)
    13. secondaryController.didMove(toParent: self)
    14. aboutController.view.isHidden = true
    15. infoController.view.isHidden = true
    16. secondaryController.view.isHidden = true
    17. }
    18. func didSelectMenuItem(named: SideMenuItem) {
    19. sideMenu?.dismiss(animated: true, completion: nil)
    20. // Don't show Name of Navigation Item (Home, About ..., etc.) in the Navigation Bar as
    21. // Title. Uncomment the next line to show the Title.
    22. //title = named.rawValue
    23. switch named {
    24. case .home:
    25. aboutController.view.isHidden = true
    26. infoController.view.isHidden = true
    27. secondaryController.view.isHidden = true
    28. case .info:
    29. aboutController.view.isHidden = true
    30. infoController.view.isHidden = false
    31. secondaryController.view.isHidden = true
    32. case .about:
    33. aboutController.view.isHidden = false
    34. infoController.view.isHidden = true
    35. secondaryController.view.isHidden = true
    36. case .second:
    37. aboutController.view.isHidden = true
    38. infoController.view.isHidden = true
    39. secondaryController.view.isHidden = false
    40. }
    41. }
    Alles anzeigen
    Die Methode addChildControllers() wird in viewDidLoad() aufgerufen.

    Gruß

    Ralf
  • ralfb schrieb:

    Siehst Du einen "drawback" bei meiner genutzten Methode oder wäre das aus Swift Sicht eine akzeptable Methode um das zu realisieren?
    Keine Ahnung, was die "Swift-Sicht" dazu sagt :) Mir erscheint in diesem Zusammenhang eine Notification aber Overkill:

    Diese würde ich verwenden, wenn der Sender eigentlich gar nicht "weiss", ob (und wie viele) Empfänger es gibt. Zum Beispiel postet die Datenhaltung eine Notification über Änderungen und diverse ViewController lauschen auf diese, ohne voneinander zu wissen. Oder das System informiert alle Apps, dass sich das iCloud-Login geändert hat ... usw.

    Bei Dir ist die Struktur statisch, es gibt eine klare Beziehung (und sogar Referenzen) zwischen den Controllern. Warum dann künstlich entkoppelt und damit die Komplexität erhöhen?

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • MyMattes schrieb:

    Warum dann künstlich entkoppelt und damit die Komplexität erhöhen?
    Aktuell eigentlich nur weil ich die Segue Geschichte nicht implementiert bekomme :( und dann klammere ich mich im Moment an das was funktioniert.
    OK, vermutlich nicht der richtige Weg.

    Vielleicht hast Du bezogen auf meinen versuch oben ja noch eine Idee warum es mit der zugegeben einfacheren Segue Implementation nicht funktioniert bzw. was ich bei mir ändern müsste.

    Gruß

    Ralf
  • ralfb schrieb:

    Vielleicht hast Du bezogen auf meinen versuch oben ja noch eine Idee warum es mit der zugegeben einfacheren Segue Implementation nicht funktioniert bzw. was ich bei mir ändern müsste.
    Sorry, ich habe keine Lust, mich in fremden Code einzulesen. Mach' die Schritte, wie von mir beschrieben, dann klappt's...

    Nicht bös' gemeint, Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Bzgl. Notification stimme ich MyMattes zu.

    Bezogen auf den ersten Post, denke ich, dass mein Vorschlag mir dem didSet eigentlich das passendste wäre.
    Der InfoViewController könnte dann so aussehen:

    Quellcode

    1. class InfoViewController: UIViewController {
    2. /// The text property for "Passing data forward with properties"
    3. var text:String = "" {
    4. didSet {
    5. print(text)
    6. }
    7. }
    8. override func viewDidLoad() {
    9. super.viewDidLoad()
    10. //view.backgroundColor = .red
    11. view.backgroundColor = UIColor(patternImage: UIImage(named: "SandSeamless.jpg")!)
    12. }
    Alles anzeigen
    Statt print(text) könntest du entsprechend anderen Code dort einfügen, z.B. um
    den Text in einem Label anzuzeigen. Teste das doch einfach einmal aus.
  • Hallo @marcoo

    ich habe das gerade mal probiert und im Prinzip funktioniert es mit den Properties zwischen den ViewControllern.
    Allerdings habe ich es bis jetzt noch nicht hinbekommen anstatt des print() den Wert in ein Label zu schreiben. Wenn ich print(text) durch

    Quellcode

    1. var queryDate:String = "" {
    2. didSet {
    3. print(queryDate)
    4. queryDateLabel.text? = queryDate
    5. }
    6. }


    ersetze stürzt die App ab. Das Datum wird noch ausgegeben, aber das setzen des Label schlägt fehl ...

    Quellcode

    1. 19.11.2020, 20:11:53
    2. Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file MySideMenu/AboutViewController.swift, line 16
    3. 2020-11-19 20:11:53.922289+0100 MySideMenu[14856:6756679] Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file MySideMenu/AboutViewController.swift, line 16
    Line 16 ist "queryDateLabel.text? = queryDate".

    Aber Danke schon mal für die Unterstützung!!

    Gruß

    Ralf
  • Ich habe noch ein wenig "gespielt" mit viewDidAppear, aber auf Grund der Implementierung mit dem SideMenu wird mein View schon früh initialisiert/geladen und das setzen des Label wird sehr früh durchgeführt und da ist die Property anscheinend noch nicht gesetzt -> es wird der default String angezeigt.

    Aus der oben gezeigten Null-Pointer Exception komme ich auch nicht raus, oder besser gesagt ich weiß nicht wie ;)

    Gruß

    Ralf
  • Hi,
    ich habe es so gemacht:
    In ViewController A

    Quellcode

    1. override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
    2. if let vcB = segue.destinationController as? VCB {
    3. vcB.vcA = self
    4. }
    5. }

    Dann in ViewController B

    Quellcode

    1. class VCB: NSViewController {
    2. let vcA: NSViewController
    3. }
    Dann kannst du im VCB beliebig auf die Elemente von VCA zugreifen.
    Manchmal schleiche ich mich mitten in der Nacht an meinen Wecker heran und brülle: "NA, WIE FÜHLT SICH DAS AN!!!"