Swift get value data from protocol - ios

i need help with my code for swift 5,
so i make a struct and protocol to store list from uitextfield and now i wanna show that data in a UiTextView in another view controller
struct PatientNote {
var note : String
init(note :String) {
self.note = note
}
}
protocol AddNotesDelegate {
func AddNotes(controller : UIViewController, notes: PatientNote)
}
class AddNotesController: UIViewController {
var delegate : AddNotesDelegate!
#IBOutlet weak var Notes: UITextView!
#IBAction func addNotes(_ sender: Any) {
if let notes = self.Notes.text {
let patientNote = PatientNote(note: notes)
self.delegate.AddNotes(controller: self, notes: patientNote)
print(patientNote.note)
}
}
}
and now i wanna show in my view controller but i get this error of "Cannot convert value of type 'PatientNote' to expected argument type 'String'" in this viewController
class NotePatientController: UIViewController, AddNotesDelegate{
func AddNotes(controller: UIViewController, notes: PatientNote) {
let NotesPatient = PatientNote(note: notes) *this is where i get the error
}
var delegate : AddNotesDelegate!
var pasien : PatientNote!
override func viewDidLoad() {
super.viewDidLoad()
PatientTextView.text = pasien.note
}
#IBOutlet weak var PatientTextView: UITextView!
//in this ibaction i edit the notes that i get from the first Vc which is AddNotesController
#IBAction func Save(_ sender: UIButton) {
if let notes = self.PatientTextView.text {
let pasienNotes = PatientNote(note: notes)
self.delegate.AddNotes(controller: self, notes: pasienNotes)
}
}
}
i try to show the note from the AddNotesController to the NotePatientController, and in the NotePatientController i can edit and save the notes in UiTextView.
so i know i must be using the protocol in a wrong way, can someone help me how should i use it? im still kinda new in swift so could probably use any help i can get, Cheer!

Change let notesPatient = PatientNote(note: notes) to let notesPatient = PatientNote(note: notes.note)

It appears PatientNote takes a String as an argument but you passed an already created PatientNote to it instead. The below syntax, using notes.note would be a cleaner solution without involving initialising a new PatientNote.
func AddNotes(controller: UIViewController, notes: PatientNote) {
print(notes.note) // access the note String like this
}

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.

How to set Struct Variable data from one view controller And Getting same value from Another view controller

Im new in swift. I have create a struct class with one variable. Im trying to set value of that struct variable from my first view controller which is working perfectly but when im trying to get that same value from second view controller its give me nil value.
below is my some codding
//Struct class
import Foundation
import UIKit
struct CompanyData {
var companyName : String?
mutating func setData(name : String)
{
companyName = name
}
}
// FIRST VIEW CONTROLLER
import UIKit
class SelfCompanyNameView: UIViewController {
#IBOutlet weak var companyNameTxt: TextfieldDesign!
var company = CompanyData()
var comName = ""
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func compnyBtnPress(_ sender: Any)
{
if companyNameTxt.text?.count == 0
{
Alert.showAlert(on: self, with: "Required", message: "Please enter your company name")
}
else
{
comName = companyNameTxt.text!
company.setData(name: comName)
print("\(comName)===\(company.companyName!)")
let vc = storyboard?.instantiateViewController(withIdentifier: "SelfAddressView") as! SelfAddressView
navigationController?.pushViewController(vc, animated: true)
}
}
}
//SECOND VIEW CONTROLLER
import UIKit
class SelfAddressView: UIViewController {
var company = CompanyData()
override func viewDidLoad() {
super.viewDidLoad()
print(company.companyName)
}
}
You need to pass your model to secondView Controller like following code
// FIRST VIEW CONTROLLER
let vc = storyboard?.instantiateViewController(withIdentifier: "SelfAddressView") as! SelfAddressView
vc.company = company
navigationController?.pushViewController(vc, animated: true)
//SECOND VIEW CONTROLLER
import UIKit
class SelfAddressView: UIViewController {
var company: CompanyData!
override func viewDidLoad() {
super.viewDidLoad()
print(company.companyName)
}
}
You need to declare a static variable as a shared instance of your struct. And use that while setting and getting your value.
E.G.
struct CompanyData {
static let shared = CompanyData()
var companyName : String?
mutating func setData(name : String)
{
companyName = name
}
}
While setting the value, use as:
company.shared.setData(name: comName)
And while getting the value, use as:
print(company.shared.companyName)
I would rather use Protocols and delegators. Here is a great tutorial will help you understand the concept Sean Allen Swift Delegate Protocol Pattern Tutorial - iOS Communication Patterns Part 1

Swift 3: prepare(for:sender:) [duplicate]

This question already has answers here:
Unexpectedly found nil IBOutlet in prepareForSegue
(2 answers)
Closed 5 years ago.
I have a simple entity:
extension Team {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Team> {
return NSFetchRequest<Team>(entityName: "Team")
}
#NSManaged public var name: String?
#NSManaged public var phone: String?
#NSManaged public var department: String?
#NSManaged public var position: String?
}
Picture for easy understanding:
I load current entity from Core Data to App successfully. Furthermore, I have a UITableViewController with property members (it's storage of fetch result from Team entity).
var members: [Team] = []
After app launching (viewDidLoad(_:)) members.count is equal to 7 and that is right. Also, all these elements of members are using by UITableVIew in UITableViewController.
My task is open Detailed View Controller who will retrieve data from tapped cell. For this delegation I use classic prepare(for:sender:) method in UITableViewController (who is segue.source in current case):
// MARK: - Navigation
extension TeamListViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == segueIdentifier,
let memberDetailsViewController = segue.destination as? MemberDetailsViewController else {
fatalError("Wrong destination!")
}
guard let selectedIndex = tableView.indexPathForSelectedRow?.row else {
fatalError("Out of range")
}
guard let name = members[selectedIndex].name,
let phone = members[selectedIndex].phone,
let department = members[selectedIndex].department,
let position = members[selectedIndex].position else {
fatalError("No correct data")
}
memberDetailsViewController.memberNameLabel.text! = name
memberDetailsViewController.memberPhoneNumberLabel.text! = phone
memberDetailsViewController.memberDepartmentLabel.text! = department
memberDetailsViewController.memberPositionLabel.text! = position
}
}
So, App launches, I tap any cell whichever I want to open for more details and.. App crashes!
fatal error: unexpectedly found nil while unwrapping an Optional value
However, my variables (name, phone, department, position) are ok. They are with values!
Oh, yes. I try to retrieve a data for UILabels in MemberDetailsViewController below:
// MARK: - IBOutlets
#IBOutlet weak var memberNameLabel: UILabel!
#IBOutlet weak var memberPhoneNumberLabel: UILabel!
#IBOutlet weak var memberDepartmentLabel: UILabel!
#IBOutlet weak var memberPositionLabel: UILabel!
What is that can be a problem here?
Because of your are set your data in prepare(for segue: UIStoryboardSegue, sender: Any?) method in TeamListViewController where MemberDetailsViewController is not loaded properly thats way you got nil.
My suggestion is: Add Object like var members: Team? in MemberDetailsViewController and pass it from TeamListViewController so error will not appear.

Property value prints but when assigned to label it shows nil

Currently working with two view controllers and a swift file dealing with the details of a store such as the phone number. There is a main ViewController and a DetailsViewController.
I currently acquire data from google places api and am able to successfully store the values in a PlaceDetails Class. Testing out the data - I am able to print to the console. However, when I try to assign a value taken from the API to a UILabel the application crashes and shows that the value of the property is nil. I am not sure why this is the case. I feel in the two view controllers I am instantiating the PlaceDetails class correctly and accessing it's properties correctly but there is something I am not aware of that is going wrong.
class ViewController: UIViewController
{
let placeDetails = PlaceDetails()
let detailsVC = DetailsViewController()
func tapLabel( sender: UITapGestureRecognizer )
{
// print statement successfully prints out the stored value as - Optional("1 888-555-5555")
print(placeDetails.phoneNumber)
// assigning value to label causes a crash stating value is nil
detailsVC.phoneNumberLabel.text = placeDetails.phoneNumber!
self.performSegueWithIdentifier("showDetailsVC", sender: self)
}
}
class DetailsViewController: UIViewController
{
#IBOutlet weak var phoneNumberLabel : UILabel!
let placeDetails = PlaceDetails()
override func viewDidLoad()
{
super.viewDidLoad()
//This approach does not cause a crash but outputs nil to the console for both the print statement and the assignment statement
print(placeDetails.phoneNumber)
phoneNumberLabel.text = placeDetails.phoneNumber!
}
}
class PlaceDetails
{
override init()
{
super.init()
}
var phoneNumber : String? //viewcontroller actions give this class property its value
}
You need to assign placeDetails to your destination view controller in prepareForSegue. I know you aren't doing this as you have created placeDetails as a let constant rather than a variable so it can't ever change from the empty PlaceDetails you originally assign.
You should declare it as an optional variable and then unwrap it properly when you use it;
class ViewController: UIViewController
{
let placeDetails = PlaceDetails()
func tapLabel( sender: UITapGestureRecognizer )
{
self.performSegueWithIdentifier("showDetailsVC", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "showDetailsVC") {
let destVC = segue.destinationViewController as! DetailsViewController
destVC.placeDetails = self.placeDetails
}
}
}
class DetailsViewController: UIViewController
{
#IBOutlet weak var phoneNumberLabel : UILabel!
var placeDetails: PlaceDetails?
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
if let placeDetails = self.placeDetails {
if let phoneNumber = placeDetails.phoneNumber {
self.phoneNumberLabel.text = phoneNumber
}
}
}
}
You can't use the value in viewDidLoad as this method will execute before the property has been set; the view controller is loaded before prepareForSegue is called, so viewWillAppear is a better place.
Try to cast your phoneNumber in a string.
detailsVC.phoneNumberLabel.text = String(placeDetails.phoneNumber!)
Also, the nil value could come from the encoding method of the response of the API.
EDIT
I think you have to set the text of your UILabel in the viewDidLoad() method of your showDetailsVC. Declare a string variable in showDetailVC, and then pass your placeDetails.phoneNumber variable to the string you just declare. (Instead of directly set the text in the tapLabel() method). Then in your
showDetailVC set the text to your UILabel in the viewDidLoad()

Is there a way for the text in a label to automatically update the display

I'm trying to make an app that lets users input 4 numbers and display what cellular network is that number. Is there a way to automatically display the numbers in the label after the user inputs the numbers without clicking a button? I actually have a code that lets the user input the number but the user must click the enter button for it to display the result on the label. Is there a way for it to automatically display the result? By the way, I'm actually new to swift so I apologize. Here is the code:
import UIKit
import Firebase
class ViewController: UIViewController, FBSDKLoginButtonDelegate {
#IBOutlet weak var numField: UITextField!
#IBOutlet weak var answerField: UILabel!
#IBAction func enterButton(sender: AnyObject) {
matchNumber()
}
func matchNumber(){
let number: String = numField.text!
let numRef = Firebase(url: "https://npi2.firebaseio.com/num_details")
numRef.queryOrderedByKey().observeEventType(.ChildAdded, withBlock: { snapshot in
let network = snapshot.value as! String!
if snapshot.key == self.numField.text! {
self.answerField.text = network
}
else {
self.answerField.text = "Missing"
}
})
}
Add a target to your numField, something like this...
override func viewDidLoad() {
super.viewDidLoad()
self.numField.addTarget(self, action: "editingChanged:", forControlEvents: .EditingChanged)
}
This will trigger the editingChanged method every time a character changes in the numField.
func editingChanged(textField:UITextField) {
if let text = textField.text {
if text.characters.count == 4 {
matchNumber()
}
}
}
Finally we check if there is a string in the input and if it is 4 characters long before running the matchNumber method.
You could use the didSet method.
var numberstring = String() {
didSet {
yourlabel.text = numberstring
}
}
If you are using textfield to take input then you can use textfieldDidEndEditing delegate method.

Resources