I am trying to use Permanent Data and Prepare For Segue together. This is what I have so far in: (View Controller 1)
override func viewDidAppear(_ animated: Bool) {
let itemsObject = UserDefaults.standard.object(forKey: "items")
if let tempItems = itemsObject as? [String] {
items = tempItems
}
in (View Controller 2):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toSecondViewController" {
let itemsObject = UserDefaults.standard.object(forKey: "items")
var items:[String]
if let tempItems = itemsObject as? [String] {
items = tempItems
items.append(textField.text!)
print(items)
} else {
items = [textField.text!]
}
UserDefaults.standard.set(items, forKey: "items")
}
}
I am attempting to add an item to an array on VC2. Then I want to transfer the array to VC1 whilst also storing it permanently. Then every time I shut down and reload the app I can print the array. The error message states "Use of unresolved identifier items". I am using Xcode 8.0 and Swift 3.0.
Ok so because you said you want to persist the data over app starts you will need the User Defaults to store your items. As Dan already suggested you are basically doing it right. You just want to set a variable that was not declared before. But I will show you this in the following code. I will also attach a second approach in which the items are passed to next view controller while performing the segue.
First example: Imagine we have two view controllers like in your example. The first view controller contains a UITextField to do user text input. Whenever we switch from the first view controller to the second view controller with the help of a storyboard segue (e.g. when pressing a button) we take the existing texts from previous segues from the User Defaults and add the current user input and then persist it back to the User Defaults. This happens in the first view controller:
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "toSecondViewController" {
let itemsFromDefaults = UserDefaults.standard.object(forKey: "items")
var items: [String]
if let tempItems = itemsFromDefaults as? [String]
{
items = tempItems
items.append(textField.text!)
}
else
{
items = [textField.text!]
}
print(items)
UserDefaults.standard.set(items, forKey: "items")
}
}
}
This first view controller looks pretty similar to your code but I wanted to add it for completeness.
Then in the second view controller we just grab the items from the user defaults and store it directly in a instance variable of this view controller. With the help of this we can do what we want in other methods within the view controller and process the items further. As I said what you were missing was the instance variable declaration to store the items in.
class ViewController2: UIViewController {
private var items: [String]? // This is only accessible privately
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.items = UserDefaults.standard.object(forKey: "items") as? [String]
}
}
Second Example: You could also declare a internal/public variable in ViewController2 so that you can set it directly from the first view controller in perform segue. Than you wouldn´t need to grab the items from the User Defaults in ViewController2. For that you can access the destination view controller of the segue then cast it to ViewController2 and directly set the items of it.
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "toSecondViewController" {
let itemsFromDefaults = UserDefaults.standard.object(forKey: "items")
var items: [String]
// [...] Do the stuff to get the items and add current input like before [...]
let destinationViewController = segue.destination as! ViewController2
destinationViewController.items = items
}
}
}
class ViewController2: UIViewController {
var items: [String]? // This is accessible from outside now
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(items) // We can now print this because it is set in prepareForSegue
}
}
I really hope I could help you with that and explained it understandable. Feel free to leave a comment if you have any questions.
You forget to write "let" or "var" in viewDidAppear. Try this:
override func viewDidAppear(_ animated: Bool) {
let itemsObject = UserDefaults.standard.object(forKey: "items")
if let tempItems = itemsObject as? [String] {
let items = tempItems
}
}
If you want to use items after if statement, then you must declare variable before if statement:
override func viewDidAppear(_ animated: Bool) {
let itemsObject = UserDefaults.standard.object(forKey: "items")
var items: [String]
if let tempItems = itemsObject as? [String] {
items = tempItems
}
}
Related
In Add_EditAddressViewController i need to show all added address in tableview, for that i have created one ARRAY and appending values to array in NewZoomAddressViewController to show in tableview but all the time i am getting single row in table view.. so here how to add value to array dynamically without replacing into oldvalue in Add_EditAddressViewController
and navigation is:
Add_EditAddressViewController: butnTap -> ProfileVC: btnTap -> NewZoomAddressViewController: btnTap -> Add_EditAddressViewController
here each time when i come to NewZoomAddressViewController need to append \(self.sublocalityName!) \(localityName!) \(self.zipName!) to addressArray to show in tableview of Add_EditAddressViewController
Note: here i have added this question related code in github: https://github.com/SwiftSamples/AddressBug here in profileVC you need to tap on map or continue Button then it navigates to NewZoomAddressViewController
class Add_EditAddressViewController: UIViewController,DataEnteredDelegate {
#IBOutlet weak var addeditTableview: UITableView!
var addressArray = [String]()
var city: String?
var pincode: String?
var locality: String?
override func viewDidLoad() {
super.viewDidLoad()
addeditTableview.register(UINib(nibName: "EditAddressTableViewCell", bundle: nil), forCellReuseIdentifier: "EditAddressTableViewCell")
print("zoooom valuew \(pincode)")
addeditTableview.reloadData()
}
}
extension Add_EditAddressViewController : UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return addressArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: EditAddressTableViewCell = tableView.dequeueReusableCell(withIdentifier: "EditAddressTableViewCell") as! EditAddressTableViewCell
cell.editButton.addTarget(self, action: #selector(editbuttonClicked(sender:)), for: .touchUpInside)
cell.nameHeader.text = "header"
cell.addressLabel.text = addressArray[indexPath.row]
return cell
}
}
NewZoomAddressViewController code:
class NewZoomAddressViewController: UIViewController {
weak var delegate: DataEnteredDelegate? = nil
var addressModel: ProfileModelUserAddress?
var addressArray = [String]()
var zipName: String?
var localityName: String?
var sublocalityName: String?
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var addressLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
print("in Zoom map VC")
mapView.delegate = self
addressLabel.text = "\(self.sublocalityName!) \(localityName!) \(self.zipName!)"
}
#IBAction func confirmBtn(_ sender: Any) {
let viewController = storyboard?.instantiateViewController(withIdentifier: "Add_EditAddressViewController") as! Add_EditAddressViewController
addressArray.append("\(sublocalityName ?? "") \(zipName ?? "") \(localityName ?? "")")
viewController.addressArray = addressArray
print("total address array all rows \(viewController.addressArray)")
navigationController?.pushViewController(viewController, animated: true)
}
}
please try to help to display all added address in tableview. i got stuck here from long time.
In your NewZoomAddressViewController replace confirm button action with
#IBAction func confirmBtn(_ sender: Any) {
for controller in navigationController?.viewControllers ?? [] {
if let listController = controller as? Add_EditAddressViewController {
let string = "\(sublocalityName ?? "") \(zipName ?? "") \(localityName ?? "")"
listController.addressArray.append(string)
navigationController?.popToViewController(controller, animated: true)
return
}
}
}
In Add_EditAddressViewController reload TableView on viewWillAppear
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isHidden=true
addeditTableview.reloadData()
}
Well what you need to do is to have address array in your profile view as well to pass it to other controller.. so your code becomes
First you will have array in profile like this
class ProfileAddressViewController: UIViewController, CLLocationManagerDelegate, UISearchBarDelegate, DataEnteredDelegate {
var addressArray = [String]()
}
Then when you call NewZoomAddressViewController you pass that array to them like this
#objc func triggerTouchAction(_ sender: UITapGestureRecognizer) {
print("Please Help!")
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "NewZoomAddressViewController") as! NewZoomAddressViewController
viewController.delegate = self
viewController.zipName = self.pincodeField.text
viewController.sublocalityName = self.colonyField.text
viewController.localityName = self.cityField.text
viewController.addressArray = addressArray
self.navigationController?.pushViewController(viewController, animated: true);
}
And in your Add_EditAddressViewController where you call profile.. assign array to profile
#objc func editbuttonClicked(sender: UIButton) {
print("in button")
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "ProfileAddressViewController") as! ProfileAddressViewController
viewController.addressArray = addressArray
self.navigationController?.pushViewController(viewController, animated: true)
}
which I add or remove from another ViewController
Im display this array.count in tableView
How in swift I can auto updates cell for array.count?
without Timer and Pull to refresh
Or how can I make refresh when loadedCart.count haw change?
Thanks
class TestTABLEVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableViewT: UITableView!
var CellT = TestTableViewCell()
var def = UserDefaults.standard
var loadedCart = [[String:Any]]()
override func viewDidLoad() {
super.viewDidLoad()
tableViewT.estimatedRowHeight = 100
tableViewT.dataSource = self
tableViewT.delegate = self
loadedCart = UserDefaults.standard.array(forKey: "cartt") as? [[String: Any]] ?? []
//Here need add auto update
}
cell for row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TestTableViewCell
let item = loadedCart[indexPath.row]
cell.nameLbl?.text = item["name"] as? String
cell.priceLbl.text = item["price"] as? String
cell.qntLbl.text = item["qty"] as? String
let im = item["image"] as? NSData
cell.PhoroUmage.image = UIImage(data: im! as Data)
return cell
}
Btn, when click - remove arr and reload cells
#IBAction func Btn(_ sender: UIButton) {
def.removeObject(forKey: "cartt")
print("remove is OK")
//Here need add auto update when btn pressed
}
If I understand you correctly, you can use reactive programming for this. For example Bond framework can be used like this:
let todoItems: SafeSignal<[TodoItem]> = ....
let tableView: UITableView = ...
class TodoItemCell: UITableView Cell { ... }
...
todoItems.bind(to: tableView, cellType: TodoItemCell.self) { (cell, item) in
cell.titleLabel.text = item.name
}
Using this framework, your table view will automatically reload when there are any changes to the source array. You will find more about usage on table views at this link.
you probably need to use notifications. i.e. send a notification when something new gets added to your cart.
Then set up an observer and, every time the observer notices a change, update the tableview with reload data.
A similar use case is listed here I think but would need to be adapted for your needs (if you need more help someone more expert than me will be able to help probably with the specifics!) Automatically Reload TableViewController On Rewind
Add
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let carts = UserDefaults.standard.array(forKey: "cartt") as? [[String: Any]] {
loadedCart = carts
}
DispatchQueue.main.async{
self.tableViewT.reloadData()
}
}
I am currently programming a diary app. Therefore I have all my entries listed in a tableView. One diary entry consists of a title, date, category and actually the diary content as a string.
All entries are stored in arrays like this:
var array = [Einträge] ()
To edit one entry, the data of one entry is passed to the "DetailViewController" by tapping on the entry in the tableView. I am currently able to change the different data, but i can't pass the changed data back to my tableView.
For showing the entry in the DetailViewController a segue is used:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
switch identifier {
case "AddEintragSegue":
let destVC = segue.destination as! AddEintragViewController
destVC.delegate = self
case "Show Detail":
let dVC = segue.destination as! DetailViewController
if let indexPath = self.tableView.indexPath(for: sender as! UITableViewCell) {
dVC.eintrag = array[indexPath.row]
}
default: break
}
}
To save the Changes, this button have to be pressed. What should I add to the following Code?
#IBAction func btnSafeChanges(_ sender: Any) {
eintrag = Einträge(name: txtTitel.text!, inhalt: txtInhalt.text!, datum: txtDatum.text!, kategorie: txtKategorie.text!)
Or should I use another type of segue?
You can go with some delegate to pass the data back
import UIKit
struct Einträge {}
protocol DetailViewControllerDelegate: class {
func newEintragCreated(eintrag: Einträge, index: Int)
}
final class InitialViewController: UIViewController {
var array: [Einträge] = []
let tableView = UITableView()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
switch identifier {
case "Show Detail":
let dVC = segue.destination as! DetailViewController
if let indexPath = self.tableView.indexPath(for: sender as! UITableViewCell) {
dVC.eintrag = array[indexPath.row]
dVC.index = indexPath.row
dVC.newEintragDelegate = self
}
default:
break
}
}
}
}
extension InitialViewController: DetailViewControllerDelegate {
func newEintragCreated(eintrag: Einträge, index: Int) {
// replace old object for newly created one
array[index] = eintrag
tableView.reloadData()
}
}
final class DetailViewController: UIViewController {
var eintrag: Einträge!
var index: Int?
weak var newEintragDelegate: DetailViewControllerDelegate?
func btnSafeChanges() {
let eintrag = Einträge()
if let index = index, let delegate = newEintragDelegate {
delegate.newEintragCreated(eintrag: eintrag, index: index)
}
}
}
I am currently trying to use Permanent Data and Prepare For Segue together. This is what I have so far: (VC1)
override func viewDidAppear(_ animated: Bool) {
let itemsObject = UserDefaults.standard.object(forKey: "items")
if let tempItems = itemsObject as? [String] {
items = tempItems
}
(VC2):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toSecondViewController" {
let itemsObject = UserDefaults.standard.object(forKey: "items")
var items:[String]
if let tempItems = itemsObject as? [String] {
items = tempItems
items.append(textField.text!)
print(items)
} else {
items = [textField.text!]
}
UserDefaults.standard.set(items, forKey: "items")
}
}
I am trying to add an item to an array on VC2. Then I want to transfer the array to VC1 whilst storing it permanently. I am using Xcode 8.0 and Swift 3.0.
Its the other way around; when your source view controller initiates a segue from either a storyboard or performSegue, the source view controller's prepareForSegue method gets invoked. With in that method you should determine the type of view controller you are segueing to and set the properties accordingly. Note that you don't really need the identifier unless you have more than one segue to the same destination in which case its sometimes useful to know which one is being invoked.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let notificationsViewController = segue.destination as? NotificationsViewController {
NotificationsViewController.notification = notification
}
}
Your question only explains.how to pass and store data permanently. But, you haven't mentioned about where you want to save data. I presume,you using tableView to store data and retrieving it.
In AppDelegate applicationDidFinishLaunching register an empty array as default value for the key "NewArray".
let defaultValues = ["NewArray": [String]()]
UserDefaults.standard.register(defaults: defaultValues)
In Second VC define newArray (variable names are supposed to start with a lowercase letter) also as empty String array
var newArray = [String]()
In viewDidLoad retrieve the array from user defaults, append the new item, save the array back to user defaults and reload the table view
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
newArray = defaults.array(forKey: "NewArray") as! [String]
newArray.append(newItem) //newItem is the data.Which you passing from First VC
defaults.set(newArray, forKey: "NewArray")
self.tableView?.reloadData()
}
and you don't need to use segue on your second VC unless you passing data from it...
I have two ViewControllers. For the first ViewController, it displays my Array data on the table. I want to get the indexPath of the selected cell, and pass this data to another ViewController.
In my First ViewController
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var nameList = [NameManager]()
#IBOutlet weak var NameTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
NameTable.dataSource = self
GetData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func GetData(){
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: NSURL(string: "http://www.json-generator.com/api/json/get/bPfifKWNaq?indent=2")!)
request.HTTPMethod = "GET"
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let data = data{
do{
let resultJSON = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
let resultArray = resultJSON as? NSArray
for jsonObjectString in resultArray!{
let code = jsonObjectString["code"] as! String
let name = jsonObjectString["name"] as! String
let description = jsonObjectString["description"] as! String
self.nameList.append(NameManager(code: code, name: name, description: description))
}
self.nameList.count
dispatch_async(dispatch_get_main_queue(), {
self.NameTable.reloadData()
})
}catch _{
print("Received not-well-formatted JSON")
}
}
if let response = response {
let httpResponse = response as! NSHTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
let count = nameList.count
return count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let myCell = NameTable.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as UITableViewCell
myCell.textLabel?.text = nameList[indexPath.row].name
myCell.detailTextLabel?.text = nameList[indexPath.row].description
return myCell
}
In my Second ViewController, which is also another class, I want to capture the indexPath of what was selected. Using the index value, I search my Array and pass that particular object to the next class. I don't have codes for this as I don't know how it works.
You could create a property outside the scope of your class and then set that within the cellForRowAtIndexPath method. That property could be accessed in your second view controller. You’ll want to look at the tableview method didSelectRowAtIndexPath as that will be where you set your property to the cell that’s been selected.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
appDelegate().index = indexPath.row
}
I’ve made a super simple project on github showing two ways of creating a property outside the scope of your view controller. One way is creating a variable within AppDelegate to be accessed via a singleton, the other is a Globals.swift file.
Hope this helps!
If you want to pass values to the controller which pops after tapping on the cell, using a singleton wouldn't be an elegant way. If you are using storyboards, then you have to use 'prepare for segue'. You implement the method in the class that handles the transfer and set all the properties in another view controller.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "SecondVC") {
// set properties
}
}