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

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.

Related

Swift Core Data - Save, Populate, Edit an Entity Attribute/Relationship

I'm pretty new to iOS dev/Core Data and am having trouble implementing one part of my workflow within my app. See below:
Core Data Properties:
item
Attributes: title, amount, date, status, category (rel), note (rel)
note
Attributes: title, contents, createdAt, updatedAt, item (rel)
When a user creates a new item, all attributes are required, except for .note as I'd like to give the user the option to create a note at a later time if only needed.
What I want to accomplish:
User selects row to display item details
On item details view, user selects notes (highlighted in yellow above) to go to Add/Edit notes
The note is just a single object that the user can enter/update the note. So basically, one note per item.
MY CODE
Xcode 11.5, Swift 5
import UIKit
import CoreData
class NoteVC: UIViewController, UITextFieldDelegate, UITextViewDelegate {
//MARK: - Core Data
var item: Item?
var context: NSManagedObjectContext!
//MARK: - Outlets
#IBOutlet weak var headerContainer: UIView!
#IBOutlet weak var headerTitle: UILabel!
#IBOutlet weak var noteView: UIView!
#IBOutlet weak var noteTitleTextField: UITextField!
#IBOutlet weak var noteContentTextView: UITextView!
#IBOutlet weak var noteDataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
override func viewWillDisappear(_ animated: Bool) {
super .viewWillDisappear(animated)
//Update note
if let title = noteTitleTextField.text, !title.isEmpty {
item?.notes?.title = title
item?.notes?.contents = noteContentTextView.text
}
item?.notes?.updatedAt = Date()
item?.notes?.contents = noteContentTextView.text
}
private func setupView() {
noteTitleTextField.text = item?.notes?.title
noteContentTextView.text = item?.notes?.contents
noteDataLabel.text = DateHelper.convertDate(date: Date())
}
//MARK: - Actions
#IBAction func doneButtonTapped(_ sender: UIButton) {
item?.notes?.title = noteTitleTextField.text
item?.notes?.contents = noteContentTextView.text
item?.notes?.createdAt = Date()
dismiss(animated: true)
}
}
MY PROBLEM
I'm having an issue creating the new note and assign it to that item and therefore populating the note details for editing. I was able to set the attributes for item.date, .category successfully to another modal view controller (so the passing of data is working), but to no avail with the Notes. Not sure if its because of the relationship or not. Again, I'm a n00b to Core Data so please forgive me for sounding simple.
Any help is appreciated.
Asking for a friend, =P
adrapp
Your problem seems to be that you are not creating a Note entity before trying to assign its properties.
The correct pattern to create a note and associate it with your item would be something like this:
if let note = NSEntityDescription.insertNewObject(forEntityName: "note", into: context) as? Note {
//set note properties
note.title = noteTitleTextField.text
note.contents = noteContentTextView.text
note.createdAt = Date()
//set relationship to item
item.note = note
}
Please verify the name of the entity ("note") and class ("Note") match what you defined in your project.
To allow updating an existing note, you need to check first if there is an existing note. You could modify the code above as follows:
// get the existing note, if any, or create a new one
let note = item.note ?? NSEntityDescription.insertNewObject(forEntityName: "note", into: context) as? Note
// if note existed or was successfully created...
if let note = note {
//set note properties
note.title = noteTitleTextField.text
note.contents = noteContentTextView.text
if item.note == nil {
note.createdAt = Date()
}
else {
note.updatedAt = Date()
}
//set relationship to item
item.note = note
}

Nil values passed after segue

I have an issue that has been driving me crazy, i had my values working inside my main VC which is named ViewController for both Age and Gender, i tested this with print statement and it works, this is below
#IBAction func uploadBtnAction(_ sender: UIButton) {
NetworkServices.instance.getGender(image: imageView1.image!) { (gender) in
self.Gender = gender
// print(self.Gender!) // This prints a value
}
NetworkServices.instance.getAge(image: imageView1.image!) { (age) in
self.Age = age
// print(self.Age!) //This prints a value
}
performSegue(withIdentifier: "updates", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "updates" {
let destinationVC = segue.destination as! SecondViewController
destinationVC.userImage = image1
destinationVC.genderFromUser = Gender
destinationVC.ageFromUser = Age
}
}
but when i segue to another VC named SecondViewController which is below, i suddenly get an error "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value", i have created variables to hold my values which will come from the first VC but this first VC does not pass the values after seque, please help, i have checked stackoverflow and tried all solutions to no avail, i though this should be the easiest part, funny enough, my image successfully passes over from the first VC to second during seque. Please advice.
import UIKit
import CLTypingLabel
class SecondViewController: UIViewController {
var userImage: UIImage?
var genderFromUser: String?
var ageFromUser: Int?
override func viewDidLoad() {
super.viewDidLoad()
imageView2.image = userImage
genderlabel.text = "Gender:"
ageLabel.text = "Age:"
print(ageFromUser!) // fatal error here
print(genderFromUser!) // fatal error here
print(userImage) // This is always successful
// ageUser.text =
// genderUser.text = userGender
// Do any additional setup after loading the view.
}
#IBOutlet weak var ageUser: UILabel!
#IBOutlet weak var genderUser: UILabel!
#IBOutlet weak var imageView2: UIImageView!
#IBOutlet weak var genderlabel: CLTypingLabel!
#IBOutlet weak var ageLabel: CLTypingLabel!
}
My goal is to set labels to the values passed over from the first VC at the second VC.
The problem is the order of operations. Your NetworkServices.instance.get... methods are asynchronous. I will use numbers to show you the order in which your code actually runs:
#IBAction func uploadBtnAction(_ sender: UIButton) {
NetworkServices.instance.getGender(image: imageView1.image!) { (gender) in
self.Gender = gender // 2 or 3
}
NetworkServices.instance.getAge(image: imageView1.image!) { (age) in
self.Age = age // 3 or 2
}
performSegue(withIdentifier: "updates", sender: self) // 1
}
So the segue is performed, and prepare is called, and the instance variables genderFromUser and ageFromUser are set, before you have set up your self.Gender and your self.Age. Therefore they are set to nil, because that's what they are at the time.
Later, of course, your asynchronous code comes along and does set self.Gender and self.Age, but it's too late; the train has left the station. SecondViewController already exists, and it has been configured with nil values.
Of course, since it does exist, you could now come along and set those instance properties again and get the SecondViewController to update its interface in response. Or you might work out some other solution. In general, this is a tricky problem — the problem of transitioning to another view controller at a time when its data is not ready yet and has to be fetched asynchronously.
[Also, you need to ask yourself, in fashioning a solution, what should happen if the data never arrives. What if the network chooses to go down at the exact moment the user taps the button? Welcome to the wild and wooly world of real life!]

Swift get value data from protocol

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
}

Store my custom Class in UserDefaults, and casting(parsing) this UserDefault to reach values (Swift 4.2)

I have created a dummy IOS Application to explain my questions well. Let me share it with all details:
There are 2 Pages in this dummy IOS Application: LoginPageViewController.swift and HomepageViewController.swift
Storyboard id values are: LoginPage, Homepage.
There is login button in Login page.
There are 3 labels in Homepage.
App starts with Login page.
And i have a class file: UserDetail.swift
And there is one segue from login page to home page. Segue id is: LoginPage2Homepage
UserDetail.swift file
import Foundation
class UserDetail {
var accountIsDeleted = false
var userGUID : String?
var userAge: Int?
}
LoginPageViewController.swift file
import UIKit
class LoginPageViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func loginButtonPressed(_ sender: UIButton) {
var oUserDetail = UserDetail()
oUserDetail.accountIsDeleted = true
oUserDetail.userAge = 38
oUserDetail.userName = "Dirk Kuyt"
UserDefaults.standard.set(oUserDetail, forKey: "UserCredentialUserDefaults")
UserDefaults.standard.synchronize()
performSegue(withIdentifier: "LoginPage2Homepage", sender: nil)
}
}
HomepageViewController.swift file
import UIKit
class HomepageViewController: UIViewController {
var result_userGUID = ""
var result_userAge = 0
var result_isDeleted = false
#IBOutlet weak var labelUserGuidOutlet: UILabel!
#IBOutlet weak var labelAgeOutlet: UILabel!
#IBOutlet weak var labelAccountIsDeletedOutlet: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.setVariablesFromUserDefault()
labelUserGuidOutlet.text = result_userGUID
labelAgeOutlet.text = String(result_userAge)
labelAccountIsDeletedOutlet.text = String(result_isDeleted)
}
func setVariablesFromUserDefault()
{
if UserDefaults.standard.object(forKey: "UserCredentialUserDefaults") != nil
{
// I need a help in this scope
// I have checked already: My UserDefault exists or not.
// I need to check type of the value in UserDefault if UserDefault is exists. I need to show print if type of the value in UserDefault is not belongs to my custom class.
// And then i need to cast UserDefault to reach my custom class's properties: userGUID, userAge, isDeleted
}
else
{
print("there is no userDefault which is named UserCredentialUserDefaults")
}
}
}
My purposes:
I would like to store my custom class sample(oUserDetail) in UserDefaults in LoginPageViewController with login button click action.
I would like to check below in home page as a first task: My UserDefault exists or not ( I did it already)
I would like to check this in home page as a second task: if my UserDefault exists. And then check type of the UserDefault value. Is it created with my custom class? If it is not. print("value of userdefault is not created with your class")
Third task: If UserDefault is created with my custom class. And then parse that value. Set these 3 variables: result_userGUID, result_userAge, result_isDeleted to show them in labels.
I get an error after I click the login button in Login Page. Can't I store my custom class in UserDefaults? I need to be able to store because I see this detail while I am writing it:
UserDefaults.standart.set(value: Any?, forKey: String)
My custom class is in Any scope above. Isn't it?
You can't store a class instance without conforming to NSCoding / Codable protocols
class UserDetail : Codable {
var accountIsDeleted:Bool? // you can remove this as it's useless if the you read a nil content from user defaults that means no current account
var userGUID : String?
var userAge: Int?
}
store
do {
let res = try JSONEncoder().encode(yourClassInstance)
UserDefaults.standard.set(value:res,forKey: "somekey")
}
catch { print(error) }
retrieve
do {
if let data = UserDefaults.standard.data(forKey:"somekey") {
let res = try JSONDecoder().decode(UserDetail.self,from:data)
} else {
print("No account")
}
}
catch { print(error) }

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()

Resources