How to move data in VC using Delegates? - ios

How can I move the data stored in data into the next VC and append it in my list when the sendDate is tapped ? Here is my code of the sending class :
protocol DataSentDelegate{
func userDidEnterData(data: String)
}
class SecondViewController: UIViewController{
var delegate: DataSentDelegate!
#IBAction func addItem(_ sender: Any)
{
let data = textField.text
delegate?.userDidEnterData(data: data)
}
Here is the code of recieving class :
class SecondPageViewController: UIViewController, DataSentDelegate{
func userDidEnterData(data: String) {
}
#IBAction func sendDate(_ sender: UIDatePicker) {
}
How do I implement list.append(data!) where data holds the value of textField.text

Do you not have to set the delegate in SecondPageViewController to self (normally in ViewDidLoad?
You declared it but don't seem to have assigned it.

You can share data through whole project using Singleton Pattern.
A singleton class is initialised for only once.
Look at these answers:
Swift - set delegate for singleton

What about you just add an array variable in your SecondPageViewController which will hold a list of strings, then append a new string each time sendDate delegate method gets called?
A few other remarks, there is no need to declare your delegate var as implicitly unwrapped if you're using optional chaining anyway, just declare it as an optional. Secondly, because SecondPageViewController is a class, it's better to make your delegate protocol class bound as such: protocol DataSentDelegate: class { func userDidEnterData(data: String) }, thirdly to avoid possible strong reference cycles make your delegate var a weak one.

Related

How to pass data to the final view controller

I am new to Swift and am building an app to learn. Right now I am making the registration section of the app.
I thought the UX would be better if there were multiple VC's asking a single question, i.e. one for your name, one for your birthdate, etc as opposed to jamming all that into a single view controller. The final view controller collects all of that information and sends a dictionary as FUser object to be saved on Firebase.
I figured I could instantiate the final view controller on each of the previous five view controllers and pass that data directly to the end. I kept getting errors and figured out that the variables were nil. It works just fine if I pass the data directly to the next view controller but it doesn't seem to let me send it several view controllers down. Obviously there's a nuance to how the memory is being managed here that I'm not tracking.
Is there a way to do what I am trying to do or do I have to pass the data through each view controller along the way?
import UIKit
class FirstViewController: UIViewController {
//MARK: - IBOutlets
#IBOutlet weak var firstNameTextField: UITextField!
//MARK: - ViewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: - IBActions
#IBAction func continueToMiddleViewController(_ sender: Any) {
let vcFinal = storyboard?.instantiateViewController(withIdentifier:
"finalVC") as! finalViewController
vcFinal.firstName = firstNameTextField.text
let vc = storyboard?.instantiateViewController(withIdentifier:
"middleVC") as! middleViewController
vc.modalPresentationStyle = .fullScreen
present(vc, animated: false)
}
...
}
import UIKit
class FinalViewController: UIViewController {
var firstName: String?
...
//MARK: - ViewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
}
...
}
TL;DR: The fastest one that would solve your problem is creating a singleton
There are many strategies for this. For a starter, it might be a good idea to read some begginer articles, like this one. I can update this answer if you don't find it useful, but it'd look just like the article
Viewcontroller's variable can't be initiated until any of the init method is called.
There are detailed answers on this thread.
Passing Data between ViewControllers
Another way to approach this problem could be to make use of closures. Note that personally I've moved away from using storyboards but I'll try to explain still. Closures are also referred to as callbacks, blocks, or in some context like here - completions.
You can declare a closure like let onSubmitInfo: (String?) -> Void below, it stores a reference to a block of code that can be executed at a later stage just like a function and it takes an optional string as a parameter just like a function can.
The closures are specified in the initialisers where a block of code is passed into the respective classes below and the closures are then called in the IBActions that will trigger the block of code that is defined where the below classes are initialised:
class First: UIViewController {
// MARK: - IBOutlets
#IBOutlet weak var firstNameTextField: UITextField!
// MARK: - Properties
private let onSubmitInfo: (String?) -> Void
init(onSubmitInfo: (String?) -> Void) {
self.onSubmitInfo = onSubmitInfo
}
// MARK: - IBActions
#IBAction func continue(_ sender: Any) {
onSubmitInfo(firstNameTextField.text)
}
}
class Second: UIViewController {
// MARK: - IBOutlets
#IBOutlet weak var lastNameTextField: UITextField!
// MARK: - Properties
private let onSubmitInfo: (String?) -> Void
init(onSubmitInfo: (String?) -> Void) {
self.onSubmitInfo = onSubmitInfo
}
// MARK: - IBActions
#IBAction func continue(_ sender: Any) {
onSubmitInfo(lastNameTextField.text)
}
}
To manage showing the above views and collecting the values returned by their closures (i.e. onSubmitInfo) we create a FlowController class that will also show the next view when the closure is called.
In FlowController we define the closures or blocks of code to be executed when it is called inside the IBAction in the respective First and Second classes above.
The optional string that is provided in the respective First and Second classes is used as the (firstName) and (secondName) closure properties below:
class FlowController: UIViewController {
private var fistName: String?
private var lastName: String?
...
private func showFirstView() {
let firstViewController = First(onSubmitInfo: { (firstName) in
self.firstName = firstName
showSecondView()
})
navigationController?.pushViewController(
firstViewController,
animated: true)
}
private func showSecondView() {
let secondViewController = Second(onSubmitInfo: { (lastName) in
self.lastName = lastName
showFinalView()
})
navigationController?.pushViewController(
secondViewController,
animated: true)
}
private func showFinalView() {
let finalViewController = Final(
firstName: firstName,
lastName: lastName)
navigationController?.pushViewController(
finalViewController,
animated: true)
}
}
The FlowController finally shows the Final view controller after it has collected the firstName form the First view controller and the lastName form the Second view controller in the showFinalView function above.
class Final: UIViewController {
let firstName: String
let lastName: String
...
}
I hope this is a shove in the right direction. I have moved away from storyboards because I find creating views in code is more verbose and clear on peer reviews and it was also easier for me to manage constraints and just to manage views in general.

Refactoring CNContactPicker UI Code using the delegate pattern in Swift

I have implemented the CNContactPickerViewController ContactsUI provided by iOS in iOS10 succesfully in a view controller so I can have a user select multiple contacts to invite to an event. I am trying to reduce the size of this single view controller by implementing the delegate pattern, and am stuck on a black screen. I have looked at a few resources, and think I am calling the delegate and defining the protocol accordingly. I have a view controller, CreateEventViewController and it implements my self defined ContactsToInviteDelegate. This protocol is as follows:
protocol ContactsToInviteDelegate : class {
//array of array of KV-pairs where inner array is {"email":"email#gmail.com", "phone": "+18965883371"}
//array of JSON objects to upload
func contactsToInvite(_ contactsStructure: [[String:String]])
}
My ContactPickerViewController self defined class is as follows:
class ContactPickerViewController: UIViewController, CNContactPickerDelegate {
//class variables
let phoneNumberKit = PhoneNumberKit()
weak var delegate: ContactsToInviteDelegate?
var contactsToSendInvitesTo = [[String:String]]()
func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
contacts.forEach { contact in
let phoneNum = contact.phoneNumbers.first
var stringPhoneNumber = String()
do{
let phoneNumber = try self.phoneNumberKit.parse((phoneNum?.value.stringValue)!, withRegion: "US", ignoreType:true)
stringPhoneNumber = "+1\(phoneNumber.adjustedNationalNumber())"
print(stringPhoneNumber)
}
catch {
print("phone number parsing error")
}
let contactDisplayName = contact.givenName
print("displayName: \(contactDisplayName)" )
let contactEmail = contact.emailAddresses.first?.value ?? ""
print("email: \(contactEmail)")
self.contactsToSendInvitesTo.append(["email":contactEmail as String, "phone":stringPhoneNumber])
}
delegate?.contactsToUpload(self.contactsToSendInvitesTo)
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
print("cancel contact picker")
}
func contactPicker(_ picker: CNContactPickerViewController,didSelectContactProperties contactProperties: [CNContactProperty]) {
}
}
And in the CreateEventViewController I am calling the delegate when i click the invite users button and implementing the method of the protocol to just attempt to print the final structure displaying contacts emails and phone numbers to send invitations to:
func selectContactsPicker() {
let cnPicker = ContactPickerViewController()
cnPicker.delegate = ContactPickerViewController() as? ContactsToInviteDelegate
self.present(cnPicker, animated:true, completion:nil)
}
func contactsToInvite(_ contactsStructure: [[String : String]]) {
print(contactsStructure)
}
This code without refactoring to try to use the delegate pattern worked before. I had all these functions within one single view controller, but with all the logic required this file itself is extending beyond 400+ lines. My problem now is that after attempting to refactor using the delegate pattern, when i click the button to trigger selectContactsPicker all I see is a black screen. I don't know what I am doing wrong, but I have a feeling it is this function itself. I am not quite sure what the body of this function should be in order to delegate the responsibility to the correct controller, or how to display it properly. Examples I saw used storyboards and segues, such as this. I looked at other examples for using delegates but I think my problem is a bit too specific and I don't know how to ask in a more general sense. If I did, I would probably not have this problem to begin with, as then I would probably properly understand how to implement the delegate pattern.
A delegate does not have to be a view controller. This is a convenient pattern when a view controller manages elements requiring delegates - rather than instantiate separate objects just let the view controller implement the protocol.
There are a number of ways to manage unruly view controllers which grow too large.
One simple way is to use extensions. To add a delegate protocol to an existing view controller:
extension SomeViewController : CNContactPickerDelegate {
... implement contact picker delegate methods
}
This can nicely compartmentalise your source code making it easier to read.
If you want to use a separate class instance as the delegate, that can be done quite easily too.
Declare your delegate class, either in the same source file or another:
class MyPickerDelegate : NSObject, CNContactPickerDelegate {
... implement contact picker delegate methods
}
note the class must inherit from NSObject, but does not need to be a UIViewController.
In the code where you fire up the contact picker:
picker = CNContactPickerViewController()
self.pickerDelegate = MyPickerDelegate()
picker.delegate = self.pickerDelegate
self.present(picker, animated: true)
Note picker view controller only keeps a weak reference to the delegate, so you must make sure to keep a strong reference to the object somewhere. Here I am using a property pickerDelegate

Trying to understand the implementation of delegates with protocols in Swift

After doing loads of research I am still a little confused about how to use and implement delegates. I have tried writing my own, simplified example, to aid my understanding - however it does not working - meaning I must be a little lost.
//the underlying protocol
protocol myRules {
func sayName(name: String);
}
//the delegate that explains the protocols job
class myRulesDelegate: myRules {
func sayName(name: String){
print(name);
}
}
//the delegator that wants to use the delegate
class Person{
//the delegator telling which delegate to use
weak var delegate: myRulesDelegate!;
var myName: String!;
init(name: String){
self.myName = name;
}
func useDels(){
//using the delegate (this causes error)
delegate?.sayName(myName);
}
}
var obj = Person(name: "Tom");
obj.useDels();
I have read and watched so many tutorials but am still struggling. I no longer get error (cheers guys). But still get no output from sayName.
which demonstrates I must be misunderstanding how delegate patterns work.
I would really appreciate a corrected version of the code, with a simple explanation as to why it works, and why it is helpful.
I hope this helps others too. Cheers.
In Swift you omit the first parameter's external name, so your function call should be delegate.sayName("Tom")
Also, it is dangerous to use an implicitly unwrapped optional for your delegate property, as you have found. You should use a weak optional:
//the underlying protocol
protocol MyRulesDelegate: class {
func sayName(name: String)
}
//the delegator that wants to use the delegate
class Person {
//the delegator referencing the delegate to use
weak var delegate: MyRulesDelegate?
var myName: String
init(name: String){
self.myName = name
}
func useDels() {
//using the delegate
delegate?.sayName(myName)
}
}
Finally, your delegate must be an object, so you can't use a delegate in the way you have shown; you need to create another class that can set an instance of itself as the delegate
class SomeOtherClass: MyRulesDelegate {
var myPerson: Person
init() {
self.myPerson = Person(name:"Tom")
self.myPerson.delegate = self
}
func sayName(name: String) {
print("In the delegate function, the name is \(name)")
}
}
var something = SomeOtherClass()
something.myPerson.useDels()
Output:
In the delegate function, the name is Tom

I'm passing data from UITableViewController to UIViewController through protocols and instead of int value I'm getting nil. Why?

I have two UITableViewController, the first one:
protocol FetchUserProfileData {
func getNumberOfRequests()
}
class ListEvents: UITableViewController{
var fetchInfo:FetchUserProfileData?
func getNumberOfRequests() -> Int{
return 12
}
and the UIViewController:
class UserProfileDetails:UIViewController, FetchUserProfileData {
var listEvents: UserListEvents?
func getNumberOfRequests(){
}
override func viewDidLoad(){
listEvents?.fetchInfo = self
print(listEvents?.getNumberOfRequests())
and this line: print(listEvents?.getNumberOfRequests()) gives me a nil value instead of 12... What's wrong here?
---- edit
Ok, now I see that listEvents is empty... So my question is how can I pass that data from ListEvents to UserProfileDetails?
In this code, listEvents is probably nil.
But, the way you use the protocol looks odd to me. I would expect:
getNumberOfRequests in the protocol to return Int
ListEvents should be implementing the protocol, not UserProfileDetails
The empty getNumberOfRequests() in UserProfileDetails should be deleted
You did not set listEvents. When you are using story boards then you should set the fetchInfo not earlier than in (overwriting) prepareForSegue. Google for examples, the web is full of them. When you segue programmatically then you can set the property not before you actually instanticated the new view controller. You are better of using listEvents!.fetchInfo = self because in that case you'll get an exception when listEvents is nil.
I made some change your code and this will pass data from ListEvents to UserProfileDetails.
protocol FetchUserProfileDelegate {
func getNumberOfRequests()->Int
}
class ListEvents: UITableViewController,FetchUserProfileDelegate{
var userProfile: UserProfileDetails?
override func viewDidLoad() {
userProfile = UserProfileDetails()
userProfile?.delegate = self
}
// MARK: FetchUserProfileDelegate
func getNumberOfRequests() -> Int{
return 12 // return as your target Int
}
}
class UserProfileDetails:UIViewController {
var delegate:FetchUserProfileDelegate?
override func viewDidLoad() {
if let _ = delegate{
let resultInt = delegate?.getNumberOfRequests() // get the Int form ListEvents
print(resultInt)
}
}
}
The idea of moving data from one controller to another is very common. Most of the time this is done using a segue. A controller can have a function called prepareForSegue. This function gets called before the transition happens. Inside the prepareForSegue function, the system gives you destination controller object. You take that object and set your data in it. When the transition happens, and your destination controller comes up, it already has the data you want to give to it.
Use Xcode and make a new project. Choose "Master-Detail Application". This will generate the code for you and it is a good example of how to pass data between controllers.

Protocols and delegate trouble in swift

All,
I have create a swift file and put a protocol in it, like this :
protocol PayButtonProtocol {
func enablePayButton()
func disablePayButton()
}
I have made my viewcontroller conform to the protocol like this :
class ViewController: UIViewController, PayButtonProtocol
I have also created the functions in the ViewController so it conforms like this
func enablePayButton() {
println("Button enabled")
PAYBarButton.enabled = true
}
func disablePayButton() {
PAYBarButton.enabled = false
}
And in another class, I have set the delegate and I want to execute the enablePayButton when something is pressed like this :
var delegate:PayButtonProtocol?
and in the function i want to execute one of the functions by :
delegate?.enablePayButton()
but it doesn't execute, what am I missing please ?
More than likely delegate is nil. Add a breakpoint and check the value of delegate before that line executes. Or change the "?" to a "!" and it will crash if delegate is nil, letting you know what's wrong.
Your code in the other class:
var delegate:PayButtonProtocol?
defines a variable named delegate that is of type PayButtonProtocol?.
The variable delegate will contain nil until you assign something to it:
delegate = <someObjectThatConformsToPayButtonProtocol>

Resources