How to read data set in first VC in other VC? - ios

I am a looking for a way to read the data set in the first view controller in another view controller
In my fist VC i have
class ScannerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AVCaptureMetadataOutputObjectsDelegate {
var scannedVisitors = [Visitor]()
.....
....
override func viewDidLoad() {
super.viewDidLoad()
// test data, will be provided server side
var visitor = Visitor(visitorName: "John", visitorCompany: "google", visitorPlace: "San", visitorPhone: "94888484", visitorEmail: "john#google.com")
scannedVisitors += [visitor]
visitor = Visitor(visitorName: "Matt", visitorCompany: "apple", visitorPlace: "Tokyo", visitorPhone: "94888484", visitorEmail: "matt#apple.com")
scannedVisitors += [visitor]
Now First view in Storyboard shows the scannedVisitors
But in an other viewController/view i like to list all scannedVisitors in a table view again, for this i have
class ListVisitorsVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var scannedVisitorsArr = [Visitor]()
override func viewDidLoad() {
super.viewDidLoad()
// first try this, later use NSUserDefaults or Core Data
let firstVC = ScannerViewController()
scannedVisitorsArr = firstVC.scannedVisitors
print("Visitors \(scannedVisitorsArr)")
}
But the scannnedVisitorsArr is empty???
1/ How can i fix the empty scannedVisitorsArr? Should i have a seperate populate scannedVisitors function in my first view controller??
2/ Is there a size restriction when using NSUserDefaults??
[EDIT]
Testing with adding a function to first VC
func readAndPopulateData(){
var visitor = Visitor(visitorName: "John", visitorCompany: "google", visitorPlace: "San", visitorPhone: "94888484", visitorEmail: "john#google.com")
scannedVisitors += [visitor]
visitor = Visitor(visitorName: "Matt", visitorCompany: "apple", visitorPlace: "Tokyo", visitorPhone: "94888484", visitorEmail: "matt#apple.com")
scannedVisitors += [visitor]
}
and
override func viewDidLoad() {
super.viewDidLoad()
self.readAndPopulateData()
Now in my other VC
let firstVC = ScannerViewController()
firstVC.readAndPopulateData()
scannedVisitorsArr = firstVC.scannedVisitors
But this just returns
Visitors [folder_name.Visitor, folder_name.Visitor]
and not the data set?
[EDIT]
Reading up on data container singleton as it looks like it's usable for this. But i read different pos/neg stories about using singletons? Any tips on the subject data container singleton are welcome.

You can use prepareForSegue Method
example
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "ToListVisitorsVC") {
//Checking identifier is crucial as there might be multiple
// segues attached to same view
var detailVC = segue!.destinationViewController as ListVisitorsVC;
detailVC.visitors = self.scannedVisitors
}
}

Related

Why passing data with delegate fails in Swift 4

This is my protocol
protocol PassDataDelegate {
func passData(data: String)
}
My first controller
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
var delegate: PassDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
delegate = SecondViewController()
}
#IBAction func sendDataButtonTapped(_ sender: Any) {
delegate?.passData(data: textField.text!)
performSegue(withIdentifier: "Go", sender: nil)
}
}
And second, final one
class SecondViewController: UIViewController, PassDataDelegate {
#IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func passData(data: String) {
print("This came from first: \(data). Will change UI.")
myLabel.text = data
}
}
App is crashing on label changing part. It says nil while unwrapping optional. What is wrong here?
SecondViewController() is not the controller designed in the storyboard. It's a brand new instance without connected outlets (which is the reason of the crash). You need the real reference to the SecondViewController instance.
Assuming the SecondViewController instance is the destination view controller of the segue you don't need protocol / delegate, pass the data through the segue
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBAction func sendDataButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "Go", sender: nil)
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Go" {
let secondController = segue.destination as! SecondViewController
controller.passedData = textField.text!
}
}
}
class SecondViewController: UIViewController, PassDataDelegate {
#IBOutlet weak var myLabel: UILabel!
var passedData = ""
override func viewDidLoad() {
super.viewDidLoad()
print("This came from first: \(passedData). Will change UI.")
myLabel.text = passedData
}
}
There are several fundamental issues with your code.
I think there might also be some misapprehensions on your side regarding delegation and UIStoryboardSegue mechanism. You should probably read up on that here (Delegation) and here (Segues).
That being said, let me post a solution to your problem with inline comments explaining what's going on.
// Has to be marked as a class protocol (`: class`) so that
// `weak var delegate: PassDataDelegate?` can be weak.
protocol PassDataDelegate: class {
func passData(data: String)
}
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
// Important!
// Make this a `weak` var. In your case, you would fortunately not create a retain cycle
// but there is a big threat of creating those when using delegation patterns with non-weak delegates.
//
// In your case, if you don't make this `weak`, `SecondViewController` would never be deallocated unless you
// cleared this var manually (setting it to `nil`).
//
// Also note that, if you're using `PassDataDelegate` solely for forwarding some data to the next view controller,
// you can dismiss this var entirely. There is no need to have a reference to the second view controller hanging around.
// In fact, as mentioned above, it can be dangerous to do so.
// Additionally, you don't need to make the protocol `: class` then.
private weak var delegate: PassDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// It doesn't make any sense to create a `SecondViewController` here.
// The segue mechanism will create a new instance of `SecondViewController`.
// delegate = SecondViewController()
}
#IBAction func sendDataButtonTapped(_ sender: Any) {
// `delegate?` is `nil` here.
// delegate?.passData(data: textField.text!)
performSegue(withIdentifier: "Go", sender: nil)
}
// This is the proper 'hook' for you to forward data or do anything with a destination
// view controller presented using `UIStoryboardSegue`.
// This method will be called by the system following your call to `performSegue`.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// `UITextField.text` can be `nil`, so safeguard for that here.
// If the destination implements our protocol, we can forward data to it.
if let text = textField.text, let delegate = segue.destination as? PassDataDelegate {
// This is optional. You can hang on to the destination view controller here, but
// review the comments above to reason about whether this makes sense or not.
self.delegate = delegate
// We can safely forward the data (text) here.
delegate.passData(data: text)
}
}
}
SecondViewController can stay as is.
Update
Regarding Delegation
The delegation pattern usually describes a back pointer which talks back to an initiating instance. E.g. using UITableViewDataSource, a UITableView talks back to a thing implementing this protocol to get information about its data and so on.You are essentially doing the opposite here by forwarding data to SecondViewController. As mentioned in the comments, this code even breaks, because the implementation of passData in SecondViewController is using outlets not yet initialised.
Now you can do one of three things here:
1
Keep the pattern you are using right now (which is not delegation to be precise) and change SecondViewController to make things work
class SecondViewController: UIViewController, PassDataDelegate {
#IBOutlet weak var myLabel: UILabel!
private var data: String?
override func viewDidLoad() {
super.viewDidLoad()
// It is safe to access `myLabel` in `viewDidLoad`. Outlets have been connected.
if let data = data {
myLabel.text = data
}
}
func passData(data: String) {
self.data = data
// Only access `myLabel` if the view is loaded.
if isViewLoaded {
print("This came from first: \(data). Will change UI.")
myLabel.text = data
}
}
}
This approach is very cumbersome actually, because you need to manoeuvre around the fact that passData may be called at any time. So you don't know if your outlets have been initialised yet, which leads to bloated and repetitive code. Bad.
2
Strip protocols entirely and use a more straightforward approach
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
// This is the proper 'hook' for you to forward data or do anything with a destination
// view controller presented using `UIStoryboardSegue`.
// This method will be called by the system following your call to `performSegue`.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// `UITextField.text` can be `nil`, so safeguard for that here.
// If the destination is a `SecondViewController`, we know that is has `public var data: String` and we can forward data to it.
if let text = textField.text, let destination = segue.destination as? SecondViewController {
// We can safely forward the data (text) here.
destination.data = text
}
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var myLabel: UILabel!
// Deliberatly marking this a `public` to make clear that
// you're intented to set this from the 'outside'.
public var data: String? {
didSet {
if isViewLoaded {
myLabel.text = data
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// It is safe to access `myLabel` in `viewDidLoad`. Outlets have been connected.
if let data = data {
myLabel.text = data
}
}
}
Again, there are things we don't like about his approach:
Still repeating code and having to check for isViewLoaded
You specifically wanted to use protocols, we don't do that here
We could work around the repetitive code issue by providing the data in an init of SecondViewController. However, since you're using segues, the storyboard will instantiate the destination view controller for you and you cannot gain control over that. Now you could strip using segues, but this quickly moves far away from your original question and is a totally different code only approach. So this is no good either.
3
Use protocols but apply the delegation pattern correctly.
protocol DataProvider: class {
func provideData() -> String?
}
protocol DataHandler: class {
var providerDelegate: DataProvider? { get set }
}
class FirstViewController: UIViewController, DataProvider {
#IBOutlet weak var textField: UITextField!
func provideData() -> String? {
return textField.text
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// If the destination is a `DataHandler`, we can set yourselves as its provider.
if let destination = segue.destination as? DataHandler {
destination.providerDelegate = self
}
}
}
class SecondViewController: UIViewController, DataHandler {
#IBOutlet weak var myLabel: UILabel!
weak var providerDelegate: DataProvider?
override func viewDidLoad() {
super.viewDidLoad()
if let data = providerDelegate?.provideData() {
// Safe to access `myLabel`, because we are in `viewDidLoad`.
myLabel.text = data
}
}
}
This approach is the most generic. Both parties don't care what exactly the handler and provider are. Note that in a classical delegation pattern, you would probably not have the DataHandler protocol and check for a concrete type (here SecondViewController) in prepareForSegue. However, this approach is more flexible while still having the delegation weaved into it. This approach is also the most robust from the SecondViewController point of view. Instead of having to handle passData at any moment, it can decide itself when to ask its delegate (DataProvider) for the data. This way, SecondViewController can reason about when all of its outlets, etc. are initialised and it is safe to process the data.

Passing data from view controller to another view controller

I have not found an exact solution for my problem. I need to pass data from one view controller to another view controller. The problem is, that after the segue, the passed string data does not appear in the label.
func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject) {
if (segue.identifier == "segue1") {
if let destination = segue.destination as? ResultsViewController {
destination.name = correctslabel.text!
The second controller: there is just variable and "name" and a UIlabel, which does not show the passed data.
import UIKit
class ResultsViewController: UIViewController {
#IBOutlet var namelabel: UILabel!
var name = ""
override func viewDidLoad() {
super.viewDidLoad()
name = namelabel.text!
}
}
I've tried many ways to do it, but none of them worked. Thank you
Finally worked for me to do an override func of it, which meant, that I had to change the sender from AnyObject to Any?.
Haven't you tried this?
// ResultsViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
namelabel.text = name
}

Making a randomizer picking words from text field (Swift)

I'm trying to make app that takes info from the two text fields and randomly selects one of the sentences and places it in a label on another view controller. I'm a student in the Mobile Apps 1 class so I'm new to this. If you could explain it as much as possible it will be greatly appreciated. Happy new year!
My code:
class twoIdeasViewController: UIViewController {
#IBOutlet weak var twoIdeaContinueButton: UIButton!
#IBOutlet weak var twoIdea2TextField: UITextField!
#IBOutlet weak var twoIdea1TextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Enter Ideas"
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
let twoIdea1:String = twoIdea1TextField.text!
let twoIdea2:String = twoIdea2TextField.text!
return true
}
func prepareForSegue(segue: UIStoryboardSegue, Object: AnyObject?){
let twoIdeaFinal = segue.destinationViewController as! twoFinalViewController
twoIdeaFinal.twoIdea = //the variable that will contain the randomizer
}
}
Make use of arc4random_uniform() to generate a random number that controls which of the two text fields you wish to extract and send text from. Also, you seem to need to fix up your prepateForSegue method: you need to match the segue identifier with the identifier of your 2nd view controller (set in attributes inspector while selecting this other view controller in your storyboard).
#IBOutlet weak var twoIdea2TextField: UITextField!
#IBOutlet weak var twoIdea1TextField: UITextField!
// ...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
/* Get the new view controller using segue.destinationViewController.
Pass the randomly chosen text view text to the UILabel of the
new view controller. */
/* Here: you need to match with the identifier of your
VC 'twoFinalViewController' (set in attributes inspector) */
if segue.identifier == "twoFinalVC" {
let viewController = segue.destinationViewController as! ViewController
let random = arc4random_uniform(2)
viewController.twoFinalLabel.text = (random == 0) ? (twoIdea1TextField.text ?? "") : (twoIdea2TextField.text ?? "")
}
}
For a detailed description covering segue communication between two view controllers (UITableViewController and UIViewController), see the following thread
Global variable and optional binding in Swift
You can use something like that
func getRandomString() -> String
{
let randomNumber = arc4random_uniform(2) + 1
switch randomNumber
{
case 1:
return twoIdea1TextField.text!
case 2:
return twoIdea2TextField.text!
default:
return ""
}
}
I have no time, but I think that with an enum is simpler than what I did.

Passing data from several VC's to a last one in Swift

As you can appreciate in this pic we have an App with three different VC's and a Last one with some variable data depending of the options selected on the previous ones.
So, for instance, in this case the user had selected a blue color, a suited style and a L as size.
Our idea is to pass data from the first VC, second VC, and the third VC to the gaps in Last VC.
Any suggestions? It would be very appreciated.
Create a model class where you can store those properties in :
class MyChoices {
var color : String? // or you could use enums for each of them
var style : String? // that would be a better choice, but for the
var size : String? // sake of simplicity I use strings in this example
}
then you pass a variable of type MyChoices from one VC to another in your prepareForSegue method
EDIT (some more info, see answer from FactorJose)
In VC 1 add your variable
class VC1: UIViewController {
#IBOutlet weak var nextOutlet: UIButton!
#IBOutlet weak var colourLabel: UILabel!
var choice : MyChoice?
...
and then further on :
#IBAction func redButton(sender: AnyObject) {
nextOutlet.hidden = false
colourLabel.text = "Red colour selected"
choice.color = "Red"
}
for all those IBActions.
then in your prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let nextVC = segue.destinationViewController as! VC2
nextVC.choice = self.choice
}
VC2 and VC3 are very similar again
Hi all! As you can appreciate in this pic we have an App with three different VC's and a Last one with some variable data depending of the options selected on the previous ones.
So, for instance, in this case the user had selected a blue color, a suited style and a L as size.
Our idea is to pass data from the first VC, second VC, and the third VC to the gaps in Last VC.
Any suggestions guys? It would be very appreciated
Code :
class VC1: UIViewController {
#IBOutlet weak var nextOutlet: UIButton!
#IBOutlet weak var colourLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
nextOutlet.hidden = true
}
#IBAction func redButton(sender: AnyObject) {
nextOutlet.hidden = false
colourLabel.text = "Red colour selected"
}
#IBAction func blueButton(sender: AnyObject) {
nextOutlet.hidden = false
colourLabel.text = "Blue colour selected"
}
#IBAction func greenButton(sender: AnyObject) {
nextOutlet.hidden = false
colourLabel.text = "Green colour selected"
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {}
}
VC2 and VC3 are the same as VC1 (same outlets and buttons)
lastVC
#IBOutlet weak var colourLabel: UILabel!
#IBOutlet weak var styleLabel: UILabel!
#IBOutlet weak var sizeLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
And the last class to store the strings
import UIKit
class MyChoices {
var colour : String?
var style : String?
var size : String?
}
What can we do?
An alternative to Glenn's answer is to use NSUserDefaults. Think of it like a mini key-value database for PropertyLists.
You can add a new key-value to it with
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(clothingColor, forKey: "Clothing Color")
And then retrieve a previously saved key-value by doing
let defaults = NSUserDefaults.standardUserDefaults()
let clothingColor = defaults.objectForKey("Clothing Color")
Since the user defaults is shared and stored in disk, the information will persist across your different view controllers. It also respects encapsulation, which is good object orientation practice, since your size controller won't have to know anything about color or style and vice versa.

Sending value back from detail to master

I am using a master-detail model in Swift.
However, I want to send a class object created in detail view back to master view. I wrote a unwind function in the master view, but I cannot see the back button in the detail view so I cannot ctrl+drag it to the exit.
Does anyone know how to set the back button to make it visible?
Rather than worrying about hooking up something to the back button, you can update the model directly as the user updates the fields in the detail view controller. To do this you can pass a reference to some model object that contains the properties to be updated (make sure that's a reference type, e.g., a class, and not a struct, though).
For example:
class Person {
var firstName: String?
var lastName: String?
}
class MasterViewController: UIViewController {
#IBOutlet weak var firstNameLabel: UILabel!
#IBOutlet weak var lastNameLabel: UILabel!
var person = Person()
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destination = segue.destinationViewController as? DetailViewController {
destination.person = person
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
firstNameLabel.text = person.firstName
lastNameLabel.text = person.lastName
}
}
class DetailViewController: UIViewController,UITextFieldDelegate {
var person: Person?
#IBOutlet weak var firstNameTextField: UITextField!
#IBOutlet weak var lastNameTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
firstNameTextField.text = person?.firstName
lastNameTextField.text = person?.lastName
}
// Note, I specified the detail view controller to be the delegate
// for the two text fields in IB: I then can detect when editing is
// done and act accordingly.
func textFieldDidEndEditing(textField: UITextField) {
switch textField {
case firstNameTextField:
person?.firstName = textField.text
case lastNameTextField:
person?.lastName = textField.text
default:
assert(false, "unidentified textField \(textField)")
}
}
}
You can have master view controller update itself in viewDidAppear, like I did above, or, better, you could add observers for the model properties. But hopefully it illustrates the basic idea.

Resources