Getting nil response while passing string to another view controller - Swift3 - swift3

In FirstViewController i'm fetching the response from JSON and want to pass that fetched response to another view controller.Below is the code which i have used so far for parsing and passing the response.
FirstViewController
var fn:String! //globally declared variable
code i have tried for parsing in FirstViewController
do {
let detailsDictionary:NSDictionary = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! Dictionary<String, AnyObject> as NSDictionary
print(detailsDictionary)
let details = detailsDictionary["Data"] as! [[String:AnyObject]]
print(details)
for dtl in details
{
self.fn = dtl["Father_Name"] as? String ?? "NA"
print(self.fn) //here i'm getting the exact value from JSON
}
}
}
SecondViewController
In SecondViewController there is a Label called profile_name and want to set that parsed string(fn) as Label's text. for that i declared another variable as global.
var pname:String!
below is the code i have used to fetch the value from FirstViewController.
viewDidLoad()
{
let othervc = FirstViewController()
self.pname = othervc.fn
self.profile_name.text = self.pname
}
Problem : I tried my best efforts to get the desired output but i'm getting nil response.
Please Help.

In Second ViewController
let strName:String!
In First ViewController
let strOne = "This is for testing"
let objstory = self.storyboard?.instantiateViewController(withIdentifier: "yout Secoond ViewController Storybord ID") as! YourSecondViewControllerName
objstory.strNam = strOne
self.navigationController?.pushViewController(objstory, animated: true)

Your updated code just won't work.
let othervc = FirstViewController()
creates a new instance of FirstViewController (not the one that got the JSON).
You should be handling it something like this:
In FirstViewController
let fn = dtl["Father_Name"] as? String ?? "NA"
let svc = SecondViewController() // Or maybe instantiate from Storyboard, or maybe you already have a reference to it
svc.pname = fn
present(svc, animated: true, completion: nil)
Then in SecondViewController
override func viewDidLoad() {
super.viewDidLoad()
profile_name.text = pname
}
I'd suggest you take some time out and re-read Apple's View Controller programming guide.
Original Answer
The problem you have here…
vcvalue.profile_name.text = fn
is that profile_name is nil as the view for the view controller hasn't been loaded at this point.
You should handle this by creating a property in LeftSideMenuViewController
var name: String?
Then set
vcvalue.name = fn
And then in LeftSideMenuViewController
override func viewDidLoad() {
super.viewDidLoad()
profile_name.text = name
}
Also, some basic tips…
Don't force unwrap (!) apart from IBOutlets. You may have to write a bit more code, but you will reduce crashes.
Make #IBOutlets private - this will prevent you accidentally assigning to them as you are now
If you're overriding any viewWill/DidDis/Appear methods, you must call super at some point.
You need to re-read the section on switch/case
So this…
let a = indexPath.row
switch(a)
{
case 0 :
if(a == 0)
{
return 45
}
break
etc
could just be…
switch indexPath.row {
case 0...4:
return 45
case 5:
return 50
default:
break
}

Related

How to present a Tabbar correctly? Unbalanced calls to begin/end appearance transitions for tabbarcontroltest.ViewController:

I have a problem showing a tabbarVC.
Here is the codes:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let tabBarVC = UITabBarController()
guard let vc1 = storyboard?.instantiateViewController(identifier: "FirstController") as? FirstController else {
print("failed to get vc1 from Storyboard")
return
}
guard let vc2 = storyboard?.instantiateViewController(identifier: "SecondController") as? SecondController else {
print("failed to get vc2 from Storyboard")
return
}
guard let vc3 = storyboard?.instantiateViewController(identifier: "ThirdController") as? ThirdController else {
print("failed to get vc3 from Storyboard")
return
}
let vc4 = UINavigationController(rootViewController: vc1)
let vc5 = UINavigationController(rootViewController: vc2)
let vc6 = UINavigationController(rootViewController: vc3)
vc4.title = "XXX"
vc5.title = "YYY"
vc6.title = "ZZZ"
tabBarVC.setViewControllers([vc4,vc5,vc6], animated: false)
tabBarVC.modalPresentationStyle = .fullScreen
self.present(tabBarVC, animated: true)
}
}
The tabbar has shown correctly, but I got a warning of "Unbalanced calls to begin/end appearance transitions for <tabbarcontroltest.ViewController:" which I don't understand.
Also I have tried to change
tabBarVC.modalPresentationStyle = .fullScreen
to
tabBarVC.modalPresentationStyle = .overFullScreen
And, then I don't have this warning, but instead, when I try to close the app by home button,
I got another warning as
tabbarcontroltest[Presentation] Attempt to present on <tabbarcontroltest.ViewController> (from <tabbarcontroltest.ViewController) which is already presenting .
I guess there is something wrong with the presentation style? Or is there something else wrong?
Thanks
found the solution by myself. the correct way is as follows:
tabBarVC.modalPresentationStyle = .fullScreen
tabBarVC.view.frame = self.view.bounds
addChild(tabBarVC)
view.addSubview(tabBarVC.view)
tabBarVC.willMove(toParent: self)

Store dictionary in UserDefaults

This is a similar approach to Save dictionary to UserDefaults, however, it is intended for SwiftUI, not using a single line like set, so I want to store the value somewhere with a variable so I can call it easily. Also it's different because I'm asking for an initialization.
I have the following:
#Published var mealAndStatus: Dictionary
init() {
mealAndStatus = ["Breakfast": "initial", "Snack": "notSet", "Lunch": "notSet", "Snack2": "notSet", "Dinner": "notSet"]
if let storedDay = UserDefaults.standard.value(forKey: "mealAndStatus") {
mealAndStatus = storedDay as! Dictionary
}
}
1- How do I correctly store that dictionary in UserDefaults in SwiftUI?
2- That init, do I have to call it at the beginning of ContentView? Or can I leave it on the other swift file like that? Not sure how the init gets called.
I already made one with bool working:
#Published var startDay: Bool
init() {
startDay = true
if let storedDay = UserDefaults.standard.value(forKey: "startDay") {
startDay = storedDay as! Bool
}
}
but the dictionary doesn't seem to work. I need to initialize that dictionary and also store it in UserDefaults so I can access it later. Any help is appreciated.
This is the perfect solution I found for SwiftUI:
Store this somewhere, in my case I created a class just for UserDefaults:
#Published var mealAndStatus: [String: Date] =
UserDefaults.standard.dictionary(forKey: "mealAndStatus") as? [String: Date] ?? [:] {
didSet {
UserDefaults.standard.set(self.mealAndStatus, forKey: "mealAndStatus")
}
}
That above initializes the dictionary and also creates a variable to be easily called and use to update the value. This can be modified at lunch time and add new values, that way is initialized with whatever I want.
Furthermore, now on Apple Dev wwdc20 they announced a new way of handling UserDefaults with SwiftUI which may be even better than the above. The propery wrapper is called: #AppStorage.
Using JSONEncoder and JSONDecoder would help you convert to data any struct or dictionary that conforms to codable.
let arrayKey = "arrayKey"
func store(dictionary: [String: String], key: String) {
var data: Data?
let encoder = JSONEncoder()
do {
data = try encoder.encode(dictionary)
} catch {
print("failed to get data")
}
UserDefaults.standard.set(data, forKey: key)
}
func fetchDictionay(key: String) -> [String: String]? {
let decoder = JSONDecoder()
do {
if let storedData = UserDefaults.standard.data(forKey: key) {
let newArray = try decoder.decode([String: String].self, from: storedData)
print("new array: \(newArray)")
return newArray
}
} catch {
print("couldn't decode array: \(error)")
}
return nil
}
// You would put this where you want to save the dictionary
let mealAndStatus = ["Breakfast": "initial", "Snack": "notSet", "Lunch": "notSet", "Snack2": "notSet", "Dinner": "notSet"]
store(dictionary: mealAndStatus, key: arrayKey)
// You would put this where you want to access the dictionary
let savedDictionary = fetchDictionay(key: arrayKey)
On a side note, you probably shouldn't be using standard defaults for storing stuff like this. Storing it as a database, or saving it in a file especially with encryption on eith the database or the file might be a bit safer.

Retrieving value from database

Hi I have a problem and would be grateful to any advice or answer.
func getUserProfileMDP(){
// set attributes to textField
var ref: DatabaseReference!
ref = Database.database().reference()
let user = Auth.auth().currentUser
print(user!.uid)
ref.child("users").child((user?.uid)!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
guard let value = snapshot.value as? [String: String] else { return }
print(value)
let passwordValue = value["password"]!as! String
print(passwordValue)
self.MDP = passwordValue // prints the right value from database
}){ (error) in
print(error.localizedDescription)
}
print(self.MDP) // prints the value innitialised in class(nope)
}
Here is the function that gets the value from database. It works (the first print gets the right value)
#IBAction func register(_ sender: Any) {
print(self.MDP)// prints the value innitialised in class(nope)
getUserProfileMDP()
print(self.MDP) // prints the value innitialised in class(nope)
let MDP = self.MDP
That is were I need the password to compare it. It doesn't get me the value of the database but the value initialized in class above:
var MDP = "nope"
Have a nice day
Given your last comment, I'd say that you're almost there, but here's an example. I did not fix the other parts of your code, I only added the completion handler in the method signature, and passed the password value to the handler, to show you how this works. The handler must be called inside the async closure.
func getUserProfileMDP(completion: #escaping (String)->()) {
// set attributes to textField
var ref: DatabaseReference!
ref = Database.database().reference()
let user = Auth.auth().currentUser
print(user!.uid)
ref.child("users").child((user?.uid)!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
guard let value = snapshot.value as? [String: String] else { return }
print(value)
let passwordValue = value["password"]!as! String
completion(passwordValue)
}){ (error) in
print(error.localizedDescription)
}
}
And you call it like that:
getUserProfileMDP() { pass in
print(pass)
self.MDP = pass
}

iOS - How to insert a new To-Do item in a single view controller?

I want to add a new To Do item when i press the add button,but i don't want to switch to another page.
press the add button to add a new row in the table view,input something and press the done button,it will save.
somebody suggests me to save the cells data to Model,but i don't know how to write this.
Who can help me?
import UIKit
import CoreData
class ToDoViewController: UIViewController {
var items: [NSManagedObject] = []
#IBOutlet weak var tableView: UITableView!
#IBAction func addItem(_ sender: UIBarButtonItem) {
//***How to write this code***
}
#IBAction func done(_ sender: UIBarButtonItem) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "ToDo", in: managedContext)!
let item = NSManagedObject(entity: entity, insertInto: managedContext)
//***let list = the current textView's text
//how to get the textView's text and assign it to a value.***
item.setValue(list, forKeyPath: "summary")
do {
try managedContext.save()
items.append(item)
} catch let error as NSError {
print("Could not save.\(error),\(error.userInfo)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self,forCellReuseIdentifier: "Cell")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "ToDo")
do {
items = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch.\(error),\(error.userInfo)")
}
}
}
extension ToDoViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let textView = UITextView(frame: CGRect(x: 0, y: 0, width: cell.frame.size.width, height: cell.frame.size.height))
cell.addSubview(textView)
textView.text = item.value(forKey: "summary") as? String
return cell
}
}
Ok so If my understanding is right you need a new row to be added if they create a new entry into your Core Data. So in your viewWillAppear you're doing a fetch. What I think you need is a:
var fetchResultController : NSFetchedResultsController<YourType>!
Then using this fetch controller you want to do the following when fetching:
private func GetToDoEntries(){
if let appDele = UIApplication.shared.deletgate as? AppDelegate{
let givenContext = appDele.persistantContainer.viewContex
let entryFetchRequest : NSFetchRequest<YourType> = YourType.fetchRequest()
let sortDescriptors = NSSortDescriptor(key: "yourEntrySortKey" , ascending: true)
entryFetchRequest.sortDescriptors = [sortDescriptors]
fetchResultController = NSFetchedResultsController(fetchRequest: entryFetchRequest, managedObjectContext: givenContext, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
do{
//Gets fetched data based on our criteria
try fetchResultController.performFetch()
if let fetchedEntries = fetchResultController.fetchedObjects{
items = fetchedEntries as? WhateverToCastTo
}
}catch{
print("Error when trying to find entries")
}
}
}
First I'm sorry but I've just written this here is stackOverflow. So what you're doing is using a fetch result controller instead of a traditional search. You are required to have the sort descriptor as well and you can try to get the results and cast them to your items or as a NSManagedObject.
Now we're not done though. Your script needs to inherit from some behaviour. At the top of your class
class ToDoViewController : UIViewController, NSFetchedResultsControllerDelegate
You need the delegate as you can see in the first block of code because we're assigning it. Now we're almost there. You just need some methods to update the table for you and these come with the delegate we just inherited from.
//Allows the fetch controller to update
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
//Allows new additions to be added to the table
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type{
case .insert:
if let _newIndexPath = newIndexPath{
tableView.insertRows(at: [_newIndexPath], with: .fade)
}
case .update:
if let index = indexPath{
tableView.reloadRows(at: [index], with: .fade)
}
default:
budgetEntryTable.reloadData()
}
if let fetchedObjects = controller.fetchedObjects{
items = fetchedObjects as! [NSManagedObject (Or your cast type)]
budgetEntryTable.reloadData()
}
}
//Ends the table adding
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
So there are 3 methods here. The first and second are very self explanatory. They begin and end the updates on your tableView. I'd also recommend that you change the name of your tableView to something other than "tableView" just for clarity.
The method in the middle uses a switch. My example is missing the "Move" and "Delete" options as I didn't required them in my project but you can add them to the switch statement.
The insert is checking the newIndexPath to see if there is one. If so then we add an array of the amount of rows required at the newIndexPath.
The update just checks the current index paths and then reloads the data on them incase you updated your data model.
I hope this is what you were looking for. Good luck! I'll try and help more if you need it but that should get you started.

NSManagedObjectContext is not saving the NSManagedObject Values in SWIFT 3.0 and XCOde 8

I am facing a problem while saving the NSManagedObject to NSManagedObjectContext in Swift 3.0 and Xcode 8. Adding the code snippets for better Understanding
let config = NSManagedObject(entity: entityDescription!, insertInto: self.moc) as! Config
Here Config class is derived from NSManagedObject
class Config: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
Assigning the Key and value to my config as below and calling a save
config.key = "access_token"
config.value = access_token
do
{
try config.managedObjectContext?.save()
}catch let error as NSError
{
NSLog(error.localizedDescription)
onCompletion("Login Failed")
return
}
This doesnt throw any error to me, but while fetching the value of access_token from NSManagedObject, value is nil
do
{
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Config")
let predicate = NSPredicate(format: "key == %#", "access_token")
fetchRequest.predicate = predicate
let fetchResults = try moc.fetch(fetchRequest) as? [Config]
if(fetchResults?.count > 0)
{
//NSLog((fetchResults?.first!.value)!)
return fetchResults?.first!.value
}
} catch let error as NSError{
NSLog(error.localizedDescription)
}
What is wrong with this piece of code?
EDIT: I can see the following code where persistentStoreCoordinator is set for managedObjectContext
lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()