Passing data back to a data model file using protocols and delegates - ios

[SOLVED] As a beginner, this most likely isn't an optimal or clever solution, however it does what it needs to do. The data from the AddVocabViewController is passed back via this delegate and then inserts a new instance of Vocab into the array in the data model. Also the delegation in the prepare for segue function allowed the data to pass through.
class ViewController: UIViewController, passNewWordData{
func passDataBack(data: Vocab){
vocabBuilder.n1LevelVocab.insert(data, at: vocabBuilder.n1LevelVocab.startIndex)
self.dismiss(animated: true) {
let currentVocabNumber = vocabNumber
self.vocabBuilder.n1LevelVocab.swapAt(currentVocabNumber, self.vocabBuilder.n1LevelVocab.startIndex)
self.loadUIN1()
print(vocabNumber)
}
}
Original Question is as follows:
I have a class modal view controller that passes data back successfully but I want to insert this data type into an array.
My modal view controller is this:
import UIKit
protocol passNewWordData {
func passDataBack(data: Vocab)
}
//pass back the data using this protocol and calling it as a delegate
var vocabBuilder1 = VocabBuilder()
class AddVocabularyViewController: UIViewController {
#IBOutlet var modalView: UIView!
#IBOutlet weak var vocabTextField: UITextField!
#IBOutlet weak var hiraganaTextField: UITextField!
#IBOutlet weak var englishTranslationTextField: UITextField!
var delegate: passNewWordData?
//this is crucial! This is the link between both VCs.
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addNewWord(_ sender: UIButton) {
guard let vocabText = vocabTextField.text, vocabTextField.hasText else {
print("Error no data")
let ac = UIAlertController(title: "Missing Vocabulary Word!", message: "Please fill in the Vocabulary field", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .cancel))
present(ac, animated: true)
return
}
guard let hiraganaText = hiraganaTextField.text, hiraganaTextField.hasText else {
print("Error no hiragana entered")
let ac = UIAlertController(title: "Missing hiragana!", message: "Please fill in the Hiragana field", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .cancel))
present(ac, animated: true)
return
}
guard let englishTranslationText = englishTranslationTextField.text, englishTranslationTextField.hasText else {
let ac = UIAlertController(title: "Missing English Translation!", message: "Please fill in the English Translation field", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .cancel))
present(ac, animated: true)
print("Error no english translation entered.")
return
}
//better to have guard lets to handle the data so conditions are correct before passing the data..
let newWord = Vocab(vocabTitle: vocabText, vocabHiragana: hiraganaText, englishTranslation: englishTranslationText, noOfTimesSeen: 0)
delegate?.passDataBack(data: newWord)
print(newWord)
}
This prints out the data that's being sent back no problem but what I want to do is add this data to a data model file I made specifically to an array inside it.
struct VocabBuilder {
var n1LevelVocab = [Vocab(vocabTitle: "上達", vocabHiragana: "じょうたつ", englishTranslation: "Improvement", noOfTimesSeen: 0),
Vocab(vocabTitle: "乗り越える", vocabHiragana: "のりこえる", englishTranslation: "To Push Through", noOfTimesSeen: 0),
Vocab(vocabTitle: "耐久性", vocabHiragana: "たいきゅうせい", englishTranslation: "Durability", noOfTimesSeen: 0),]
Things I've tried:
I tried to make the extension to the the main view controller to receive the data but it won't append the data when I call the functions from the data model to add them.
extension ViewController: passNewWordData {
func passDataBack(data: Vocab) {
self.dismiss(animated: true)
self.vocabInfo.n1LevelVocab.append(data)
//vocabInfo is calling the VocabBuilder Struct.
//need to append this data to the newWord variable then send that back to the n1VocabArray in the dataModel.
print(data)
}
}
I tested out inside the data model appending a new Vocab type and it does append an extra item to the array there, but how do I get that data from my modal view controller all the way back to that struct? Would I need to create an extension to handle the data from the protocol in my data model itself? Most examples are just passing directly into the view controller.
Any help would be appreciated.

Related

How to filter array from selected tableviewcell in swift?

I am quite puzzled on how will I construct my codes regarding on how I will filter the selected array from a tableviewcell. The JSON below is the content of the tableview which displays like
[
{
"hospitalNumber": "00000001",
"patientName": "Test Patient",
"totalAmount": 1111.3
},
{
"hospitalNumber": "00000002",
"patientName": "Test Patient 2",
"totalAmount": 1312
},
{
"hospitalNumber": "00000003",
"patientName": "Test Patient 3",
"totalAmount": 475
}
]
The problem is how can I display the selected hospitalNumber and patientName in the next View Controller, which will display like
This is what my `PaymentDetailsViewController' have:
var patientList: [Patient]! {
didSet {
latestCreditedAmountTableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
getPatientList()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showPatientPaymentDetailsVC" {
if let patientPaymentDetailsVC = segue.destination as? PatientPaymentDetailsViewController {
patientPaymentDetailsVC.isBrowseAll = self.isBrowseAll
if !isBrowseAll {
patientPaymentDetailsVC.patientPayoutDetails = self.selectedPatientPayment
patientPaymentDetailsVC.currentRemittance = self.currentRemittance
patientPaymentDetailsVC.doctorNumber = self.doctorNumber
}
}
}
}
func getPatientList() {
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.show(withStatus: "Retrieving Patient List")
APIService.PatientList.getPatientList(doctorNumber: doctorNumber, periodId: currentRemittance.periodId) { (patientListArray, error) in
guard let patientListPerPayout = patientListArray, error == nil else {
if let networkError = error {
switch networkError {
case .noRecordFound:
let alertController = UIAlertController(title: "No Record Found", message: "You don't have current payment remittance", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
case .noNetwork:
let alertController = UIAlertController(title: "No Network", message: "\(networkError.rawValue)", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
default:
let alertController = UIAlertController(title: "Error", message: "There is something went wrong. Please try again", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
}
}
SVProgressHUD.dismiss()
return
}
self.patientList = patientListPerPayout
self.latestCreditedAmountTableView.reloadData()
SVProgressHUD.dismiss()
return
}
}
**getPerPatientPAyoutDetails(from: String) function**
func getPerPatientPayoutDetails(from: String) {
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.showInfo(withStatus: "Retrieving Patient Details")
APIService.PatientList.getPatientDetailsPerPayout(periodId: currentRemittance.periodId, doctorNumber: doctorNumber, parameterName: .selectedByHospitalNumber, hospitalNumber: from) { (patientPayout, error) in
guard let patientPerPayoutDetails = patientPayout, error == nil else {
if let networkError = error {
switch networkError {
case .noRecordFound:
let alertController = UIAlertController(title: "No Record Found", message: "You don't have current payment remittance", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
case .noNetwork:
let alertController = UIAlertController(title: "No Network", message: "\(networkError.rawValue)", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
default:
let alertController = UIAlertController(title: "Error", message: "There is something went wrong. Please try again", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
}
}
SVProgressHUD.dismiss()
return
}
self.selectedPatientPayment = patientPerPayoutDetails
print(self.selectedPatientPayment)
SVProgressHUD.dismiss()
return
}
}
Base on the gePatientList() function, it will just pull the full list of the patients. I don't know how I will pass the data of the selected patient to another VC. Hope you can help me. Thank you so much.
Codes that might help to understand the flow of my codes
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath.section {
case 0: break
case 1: let selectedpatient = patientList[indexPath.row].hospitalNumber
print(selectedpatient!)
self.isBrowseAll = false
getPerPatientPayoutDetails(from: selectedpatient!)
default: break
}
}
Below is the another View Controller that will display the patientName and hospitalNumber
PatientPaymentDetailsVC
class PatientPaymentDetailsViewController: UIViewController {
#IBOutlet weak var patientProcedureTableView: UITableView!
#IBOutlet weak var hospitalNumberLabel: UILabel!
#IBOutlet weak var patientNameLabel: UILabel!
var currentRemittance: CurrentRemittance!
var doctorNumber: String!
var isBrowseAll: Bool!
var patientList: [Patient]!
var patientPayoutDetails: [PatientPayoutDetails]!
override func viewDidLoad() {
super.viewDidLoad()
setupPatientInfo()
}
//MARK: FUNCTION
func setupPatientInfo() {
self.patientNameLabel.text = patient.patientName
self.hospitalNumberLabel.text = patient.hospitalNumber
}
The pulled data under the getPerPatientPayoutDetails function from the didselect will be displayed in PatientPaymentDetailsVC. Below is the output, as you can I see I can pull the data under getPerPatientPayoutDetails but the patientName and hospitalNumber does not display the data.
First of all don't get the data from the table view cell, get it from the data source
Connect the segue to the cell.
Delete the entire method didSelectRowAt
When prepare(for segue is called the sender parameter is the cell.
Get the index path from the cell and get the patient at that index path.
Rather than passing multiple parameters declare a var patient : Patient! property in the destination controller and hand over the patient instance.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "showPatientPaymentDetailsVC",
let cell = sender as? UITableViewCell,
let indexPath = tableView.indexPath(for: cell) else { return }
let patient = patientList[indexPath.row]
getPerPatientPayoutDetails(from: patient.hospitalNumber)
let patientPaymentDetailsVC = segue.destination as! PatientPaymentDetailsViewController
patientPaymentDetailsVC.patient = patient
patientPaymentDetailsVC.patientPayoutDetails = self.selectedPatientPayment
patientPaymentDetailsVC.currentRemittance = self.currentRemittance
patientPaymentDetailsVC.doctorNumber = self.doctorNumber
}
class PatientPaymentDetailsViewController: UIViewController {
#IBOutlet weak var patientProcedureTableView: UITableView!
#IBOutlet weak var hospitalNumberLabel: UILabel!
#IBOutlet weak var patientNameLabel: UILabel!
var currentRemittance: CurrentRemittance!
var doctorNumber = ""
var isBrowseAll = false
var patient : Patient!
var patientPayoutDetails: [PatientPayoutDetails]!
override func viewDidLoad() {
super.viewDidLoad()
setupPatientInfo()
}
//MARK: FUNCTION
func setupPatientInfo() {
self.patientNameLabel.text = patient.patientName
self.hospitalNumberLabel.text = patient.hospitalNumber
}
Side note:
Don't declare patientList as implicit unwrapped optional, declare it as non-optional empty array
var patientList : [Patient]()
Use tableView(_:didSelectRowAt:) method by conforming to UITableViewDelegate.
Get the selected patient as displayed below:
selectedPatient = tableView[indexpath.row] as! [String:Any]
As per your edited question, try this:
let patient = patientList[indexPath.row] as! Patient

Custom Barcode scanner, cannot pass back scanned data

I am having trouble passing back data that is scanned by my custom barcode scanner.
The data is read successfully and I am able to assign the value to a variable. But I cannot pass the data back to the previous view controller to populate a text view.
I am using this below to pass to my barcode VC to store the data inside it
var barcodeScanData: String = ""
I am using prepare for segue below to
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "BarcodeScanVC" {
let desitnationVC = segue.destination as! BarcodeScanVC
desitnationVC.xyz = barcodeScanData
}
}
Below here is where I am attempting to send back the data from my custom barcode scanner
var xyz: String = ""
func launchApp(barcodeScan: String) {
if presentedViewController != nil {
return
}
let alertPrompt = UIAlertController(title: "Barcode Found", message: "\(barcodeScan)", preferredStyle: .actionSheet)
let confirmAction = UIAlertAction(title: "Confirm", style: UIAlertAction.Style.default, handler: { (action) -> Void in
let barcodeData = PartsVCDetail()
self.xyz = barcodeScan
barcodeData.barcodeScanData = self.xyz
print(self.xyz, "This is what I am sending")
print(barcodeData.barcodeScanData, "This is what I am sending it TO" )
self.navigationController?.popViewController(animated: true)
})
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel, handler: nil)
alertPrompt.addAction(confirmAction)
alertPrompt.addAction(cancelAction)
present(alertPrompt, animated: true, completion: nil)
}
The two print lines
print(self.waybill, "This is what I am sending")
print(barcodeData.barcodeScanData, "This is what I am sending it TO"
Show me the correct scan data, however, when I use the last line below:
self.navigationController?.popViewController(animated: true)
The data is lost and I see an empty value in my viewDidAppear on the first view controller:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
print(barcodeScanData, "This is empty but it shouldnt be")
dataFromBarcodeScanner.text = barcodeScanData
}
What am I missing ?
With this code let barcodeData = PartsVCDetail() you are creating a new instance of PartsVCDetail and then setting the property of that instance. As soon as the action ends this instance will be deallocated and you will return to the previous view controller via popViewController.
A common solution to your requirement is a delegation pattern.
You declare a protocol for your delegate to implement
You have the original view controller implement this delegate protocol
You have the original view controller set itself as the second view controller's delegate
The second view controller can invoke the delegate method to pass the data back
Protocol
protocol BarcodeScanDelegate {
func didScan(barcodeData: String)
}
PartsVCDetail
class PartsVCDetail: UIViewController, BarcodeScanDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let desitnationVC = segue.destination as? BarcodeScanVC {
desitnationVC.delegate = self
}
}
func didScan(barcodeData: String) {
self.barcodeScanData = barcodeData
}
}
BarcodeScanVC
var delegate: BarcodeScanDelegate?
func launchApp(barcodeScan: String) {
guard presentedViewController == nil else {
return
}
let alertPrompt = UIAlertController(title: "Barcode Found", message: "\(barcodeScan)", preferredStyle: .actionSheet)
let confirmAction = UIAlertAction(title: "Confirm", style: UIAlertAction.Style.default, handler: { (action) -> Void in
self.delegate?.didScan(barcodeData: self.xyz)
self.navigationController?.popViewController(animated: true)
})
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel, handler: nil)
alertPrompt.addAction(confirmAction)
alertPrompt.addAction(cancelAction)
present(alertPrompt, animated: true, completion: nil)
}

First program in Swift - HelloWorld

I have some issue with my first code in Swift, when I'm running it, it seems fine, but it shows e.g. Hello Optional "(name)" instead of Hello (name).
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var helloLabel: UILabel!
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var sayHelloButton: UIButton!
#IBAction func sayHelloAction(sender: AnyObject)
{
let name = nameTextField.text
if name!.isEmpty {
let alert = UIAlertController(title: "Error", message: "Please enter a name", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
} else {
helloLabel.text = "Hello \(name)!"
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setupUI()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setupUI() {
helloLabel.text = "Hello There!"
helloLabel.textColor = UIColor.blueColor()
helloLabel.textAlignment = NSTextAlignment.Center
nameTextField.placeholder = "Enter your name"
sayHelloButton.setTitle("Say Hello", forState: .Normal)
}
}
Can anyone help me, please?
Doms.
In Swift you have a type called Optional. This is used to express nullability. You need to do what is called unwraping the optional. I will discourage you from force unwrapping ! it will cause your app to crash. You can unwrap an optional value using the if let statement:
#IBAction func sayHelloAction(sender: AnyObject) {
if let name = nameTextField.text where !name.isEmpty {
helloLabel.text = "Hello \(name)"
} else {
let alert = UIAlertController(title: "Error", message: "Please enter a name", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}
I ran into this when I was just learning Swift myself, and it tends to pop out in all sorts of places. For instance, I couldn't understand why one of my tableview columns always said "0" instead of "1" or "2". It was in fact saying "O"ptional!
The simplest solution is to guard everything everywhere. That way the compiler knows you've checked that it's not nil, and "unwraps" the value for you. so instead of:
let name = nameTextField.text
just do a:
guard let name = nameTextField.text else { return }
Replace that return with something more appropriate. From that point on, name is the string and won't have the Optional(theThingYouReallyWanted) any more.
note: as I said at the start, this has a habit of showing up in weird places. If you bind Optionals into things like text fields or table columns, you'll see it when you don't expect it. I strongly suggest making getter/setter properties for all UI work, and using the guard to unwrap as above and instead of return hand back an empty string or something similar.
You get the Optional("") because the optional value is not unwrapped. You need to put a ! after the object and you won't get the Optional("") bit any more. I would show you the code but you haven't shown us the print() statement. I made some sample ones below that I think would replicate the problem, though I haven't tried them.
var value:String?
value = "Hello, World"
print("The Value Is \(value)") // Prints "The Value Is Optional(Hello, World)"
print("The Value Is \(value!)")// Prints "The Value Is Hello, World"

Pass data from detail view to master - Swift

I'm new in swift, and I've been searching this answer for weeks, but I still haven't found it:( What I'm building is a master-detail app. It is a reminders app.The user is able to edit those reminders in the detail view, but the problem comes when I try to pass the edited data back to the master view, and add it to an array of arrays that I use as the database. I've tried with an unwind segue, but I don't know how to add in the right place the data that I have passed back. I would really appreciate your help!
//MAIN VIEW
var addMoneyArray = [[Int]]()
class GonMainTableViewController: UITableViewController {
//MARK: - VARIABLES
var recivedData : Int?
//MARK. - IB OUTLETS
#IBOutlet var tableView1: UITableView!
#IBAction func unwindViewController(segue: UIStoryboardSegue){
if(segue.sourceViewController .isKindOfClass(GonDetailViewController))
{
let view2 : GonDetailViewController = segue.sourceViewController as! GonDetailViewController
recivedData = view2.passTxtFieldValue
}
}
enter code here
//VIEW2
class GonDetailViewController: UIViewController {
var passTxtFieldValue : Int?
#IBAction func addBtn(sender: AnyObject) {
var returnMoney = Int(addMoneyTxtField.text!)
returnMoney = passTxtFieldValue
let alertController = UIAlertController(title: "Awesome!", message: "Succesfully saved your data!", preferredStyle: .Alert)
let takePhotoAction = UIAlertAction(title: "OK", style: .Default) { (Alert) -> Void in
self.performSegueWithIdentifier("returnToMain", sender: self)
}
alertController.addAction(takePhotoAction)
presentViewController(alertController, animated: true, completion: nil)
}

UIAlertController in swift

I am creating a view controller in swift with a few text fields and an accept button which confirms the user's input. The accept button also checks if any of the text fields is empty. If so, it will pop up an alert saying something like it cannot be empty. if it is not empty, it will store the input and then jump to another view.
I created an separated function called checEmpty() which looks like this:
func checEmpty(title: String, object: UITextField) -> (Bool) {
if object.text.isEmpty {
let alertController = UIAlertController(title: "Invalid input",
message:"\(title) cannot be empty",
preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss",
style: UIAlertActionStyle.Default)
self.presentViewController(alertController, animated: true, completion: nil)
return false
} else {
return true
}
}
And I call this function in the acceptButton action:
#IBAction func acceptButton(sender: UIButton){
if(checEmpty("Event", object: eventName) && checEmpty("Priority", object: Priority)
{
//if not empty, confirm the user input
// ...
}
When I run it, the alert message works fine but for some reason the console shows this:
2015-08-03 12:11:50.656 FinishItToday[13777:688070] >'s window is not equal to
's view's window!
Can anyone tell me why this warning appears? Thank you very much!
PS.
What I want it to do is that if any of the text field is empty, show the alert and then stay at the same page. If none of them are empty, then perform the segue and switch to another view. The code above works fine except the warning.
Here is your working code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var eventName: UITextField!
#IBOutlet weak var Priority: UITextField!
#IBAction func acceptButton(sender: UIButton){
if checEmpty("Event", object: eventName) && checEmpty("Priority", object: Priority){
println("Both Text Fields Are Empty")
}
}
func checEmpty(title: String, object: UITextField) -> (Bool) {
if object.text.isEmpty {
var Alert = UIAlertController(title: "Invalid input", message: "\(title) cannot be empty", preferredStyle: UIAlertControllerStyle.Alert)
Alert.addAction(UIAlertAction(title: "Dismiss", style: .Cancel, handler: { action in
println("Click of cancel button")
}))
self.presentViewController(Alert, animated: true, completion: nil)
return false
} else {
return true
}
}
}
Use this code to for alert view controller in swift. It may help you.
import UIKit
protocol alertViewDelegate {
func actionActive(index:Int, tag:Int)
}
class AlertView: NSObject {
var delegate:alertViewDelegate!
func showAlert(title:String, message:String, actionName:NSArray, tag:Int) -> UIAlertController {
var alertController:UIAlertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
for str: AnyObject in actionName {
let alertAction:UIAlertAction = UIAlertAction(title: str as! String, style: UIAlertActionStyle.Default, handler: { (action) -> Void in
self.delegate.actionActive(actionName.indexOfObject(str), tag:tag)
})
alertController.addAction(alertAction)
}
return alertController;
}
}

Resources