How to present data, saved in Realm, in another viewController's tableViewCells? - ios

I'm trying to make an app, in which one of the functions is, that in one viewController, the user can enter some data about a given person (a photo, a name and an answer to a certain question). When pressing the "save new person-button", the user is to be redirected to the previous viewController, which amongst other things holds a tableView.
In this tableView, I want there to be created a new cell for each time the user presses the "save new person-button" in the other viewController. The cell should, of course, hold the person's name (and ideally also a miniature of the photo).
Now, it is important that the data is stored 'internally' - also after exiting a certain viewController or closing the app. Therefore I'm using Realm as a database to store all the data.
I'm very new when it comes to using Realm, so maybe my questions seem stupid. Basically, I think I have been able to make the "Save new person-button" save all the data in the realm database. But when trying to make the button create new cells (which must stay, once created!!) in the previous viewController... All kinds of weird things happen!
What the code underneath shows is my current attempt on passing at least the name through a segue, but that creates several problems: The cells don't stay. If you try to add a new cell, it just overrides the old one. And finally, the PeopleData.count apparently counts the number of letters in the name - and creates fx. 5 identical cells for a 5 letter long name! :D
I'm sure this is not the right way to pass the data ... So what is the best way to present this data? So that the cells stay once created - and so that the user can add several new people/several new cell without overriding the old ones!
Relevant code:
viewController where the user can enter the data:
#IBAction func SaveNewPerson() {
let NewPerson = person()
NewPerson.name = PersonName.text!
NewPerson.answer = PersonAnswer.text!
NewPerson.extraIdentifier = PersonExtraIdentifier.text!
let realm = try! Realm()
try! realm.write {
realm.add(NewPerson)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// let allPeople = realm.objects(person.self)
let SomePersonName = PersonName.text!
if segue.identifier == "PassingSomeInfo" {
if let vc = segue.destination as? AllPersonsInYourDiary {
vc.PeopleData = SomePersonName
}
}
Viewcontroller with the tableView:
var PeopleData: ""
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return PeopleData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Person1", for: indexPath)
cell.textLabel?.text = self.PeopleData
cell.textLabel?.numberOfLines = 0
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
return cell
}
Third viewController (used to store all the person-data):
class person: Object {
#objc dynamic var name = ""
#objc dynamic var answer = ""
#objc dynamic var extraIdentifier = "" }
Thanks!

This question is honestly a little to broad. What you are asking for are several things at once. And if I were to write the implementation for you I wouldn't answer your actual question on what you need to do. What I think you are asking is this:
How do I write and retrieve data from realm.
How do I structure my code for passing data between views.
So my recommendation is this.
Don't pass the data with a segue. Create a class that will access Realm and retrieve the data you need for that view. Name it something like Database, Realm or LocalStorage.
In that class add methods for writing to Realm and retrieving from Realm. How to do that you can find in the Realm documentation: https://realm.io/docs/swift/latest/
In your view that needs to display the entries instantiate your Realm class. Some psuedo code to help you on the way:
class ViewController {
fileprivate let database = LocalStorage()
fileprivate var peopleData: [People]()
viewDidLoad() {
super.viewDidLoad()
database.retrieveData(primaryKey: "extraIdentifier", completionHandler: { entries, err in
if err == nil {
self.peopleData = entries
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return peopleData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
let person = self.peopleData[indexPath.item]
let cell = tableView.dequeueReusableCell(withIdentifier: "Person1", for: indexPath)
cell.textLabel?.text = person.name
cell.textLabel?.numberOfLines = 0
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
return cell
}
}

After you call
try! realm.write {
realm.add(NewPerson)
}
you already saved on realm so you don't have to pass this on the segue you can retrieve from realm on the next view controller by calling
realm.objects(ofType: NewPerson.self)
This will return an array of NewPerson or you can also search just one with your primary key
realm.object(ofType: NewPerson.self, forPrimaryKey: yourKey)

Related

How can I save the state of UISwitches on a TableViewController when navigating through different view controllers/table view controllers?

My checklist application has different sections where the main screen (UIViewController) with several buttons leading to various food items (Vegetables, Fruits, etc.). The buttons lead to a TableViewController page, where you can add items via "+" button that modally presents an "Add Item" view controller.
After adding items to the list, it puts the name of the item and a UISwitch at the far right. This switch represents the check part of the checklist. However, regardless of what state you leave it on, when you navigate between different subsections (like going from Vegetables List -> Home -> Fruits List -> Home -> Back to Vegetables List) and come back to the original list, all the switches are off.
I have an idea of what I have to do: UserDefaults. My idea is to store the state of the switch when the state is changed and then in the viewDidLoad function iterate through all visible elements and set the state of the switches to their saved states. However, I'm not sure where to start with this idea.
Snapshot of a portion of my storyboard that depicts the design for my list area and my add items area
The list TableViewController is linked to a class called MEListViewController. Problem here is when I try to make an outlet in the ViewController class, it says that
I'm not allowed to put Outlets on repeating objects.
So, How can I save the state of Switches and keep them in their user-selected state even when you navigate through different screens?
Currently switches can be toggled while looking at a list. Leave the list and come back to it and the switches are reverted to the default "OFF" state.
To address your this problem
"it says that I'm not allowed to put Outlets on repeating objects."
You need have a custom UITableViewCell class and then you can have UISwitch outlet there.
Then to save a UISwitch state for further use
You can store only the selected cell index as state in UserDefault and check the same against the current index in cellForRowAt tableView method.
If it is found then your last state is ON otherwise default one is OFF.
This code snippet will work as you expect:
Custom UITableViewCell class
class CustomCell: UITableViewCell {
#IBOutlet weak var switchObj: UISwitch!
#IBOutlet weak var textValue: UILabel!
#IBAction func toggleState(_ sender: Any) {
var loadValues: [Int: Bool]!
if let values = Memory.getStatesFromMemory() {
loadValues = values
} else {
loadValues = [Int: Bool]()
}
loadValues[switchObj.tag] = switchObj.isOn
Memory.saveSwitchStatesToMemory(savedStates: loadValues)
}
}
UIViewController class
class CustomTableView: UIViewController, UITableViewDelegate, UITableViewDataSource {
var savedStates: [Int: Bool]!
override func viewDidLoad(){
super.viewDidLoad()
if let values = Memory.getStatesFromMemory() {
savedStates = values
} else {
savedStates = [Int: Bool]()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as? CustomCell
cell?.switchObj.isOn = savedStates[indexPath.row] == nil ? false : savedStates[indexPath.row]!
cell?.switchObj.tag = indexPath.row
cell?.textValue.text = "Switch stored state >>"
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
}
Custom class to use for save/retrieve values from memory
class Memory {
static func getStatesFromMemory() -> [Int: Bool]? {
if let switchValues = UserDefaults.standard.object(forKey: "SwitchStates") as? Data {
let decodedTeams = NSKeyedUnarchiver.unarchiveObject(with: switchValues) as! [Int: Bool]
return decodedTeams
}
return nil
}
static func saveSwitchStatesToMemory(savedStates: [Int: Bool]) {
let encodedData = NSKeyedArchiver.archivedData(withRootObject: savedStates)
let userDefaults = UserDefaults.standard
userDefaults.set(encodedData, forKey: "SwitchStates")
UserDefaults.standard.synchronize()
}
}
For a simple implementation you could have an array of Item objects (below). When you toggle a switch, figure out which cell's switch was toggled, then update the isOn state in the corresponding Item.
When you dequeue a cell before displaying it, make sure to update the UISwitch's state based on the value of the corresponding Item.
Here's a basic item class that can get you started.
class Item {
var isOn: Bool
}

Save Firebase structure in UITableView Array [Swift 4]

I am new to programming and currently trying to make a newsfeed like app. My goal is it to notice changes in the firebase database through my app and new posts in my table view. Currently I am able to show one post in my tableView. Its only changing though when you reopen the ViewController.
One of my problem is that I don't know how to use any other command then obserSingleEvent and I am pretty sure that one of my main mistakes.
So my questions are:
1. How do I make the tableView instantly reload when a change appears?
2. how do I display more then one post?
(3. how can I show the most recent posts first?)
class ViewController: BaseViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var newsfeedTableView: UITableView!
var ref: DatabaseReference!
var posts = [String]()
override func viewDidLoad() {
super.viewDidLoad()
addSlideMenuButton()
ref = Database.database().reference()
ref.child("posts").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { snapshot in
let newPost = snapshot.value as? String
self.posts.append(newPost!)
print("newPost: \(newPost)")
print("posts: \(self.posts)")
self.newsfeedTableView.reloadData()
print("reloadData")
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (posts.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
cell.textLabel?.text = posts[indexPath.row]
print("posts in cell: \(posts)")
return cell
}
}
You're observing with singleEvent which is wrong for the intended use.
ref.child("posts").observe( .childAdded, with: { snapshot in
let newPost = snapshot.value as? NSDictionary
let post_title:String = newPost!["post_title"] as? String ?? "error"
self.posts.append(post_title)
print("newPost: \(newPost)")
print("posts: \(self.posts)")
self.newsfeedTableView.reloadData()
print("reloadData")
})
Also, that sorting system won't work for a news app. you can't work with human-provided 1,2,3. could you update it to something like this from now on:
(1 - 2) A random-unique generated post ID
PS. It's impossible for Firebase to order your post list with post, post1, post2. So in this case queryOrderedByKey() is useless.
Making the posts 1,2,3 would work for the ordering system
1) You can implement a function that is constantly observing the data in Firebase for changes and update the array. But be careful with that, it will be consuming memory and also you will get a lot of network usage.
.observe // instead of .observeSingleEvent will do that
Again, it would be better if you add a refresher to the tableView so you fetch the data on demand instead of all the time.
2) Your array is fine. I'll move the call to firebase to another function instead of having it in viewDidLoad().
3) You need to store a timeStamp in firebase to get this functionality. Yes, you can queryOrdered but remember firebase is async. Data will be given as available so it would be better to sort the array depending on the creation timeStamp of each of the items.

UITableView first row '0' wont update upon table reload - all others do?

I have a one view app with embedded UITableView that displays a list of "stores"(Realm object). By default I populate the table view of all the Store objects. IF the user wants to then narrow the results they can do so by using any combination of text fields in MasterVC. When they hit search - simply update TableView with 'filtered' Realm objects.
What works:
Populate UITableView with objects from the Realm.
Create new Realm entries via text field entries in MasterVC and repopulate table in ResultsVC.
Swipe to delete object on table / and Realm object.
What sort of works:
If user enters a search term then 'filter' the Realm object (Stores) and repopulate the table. This correctly reloads and returns the number of results. However the First Cell (0) of the TableView is always the exact same and never updates.. If there are 20 returned results in the search then Rows 1-18 are correctly displayed. Row 0 is static and never changes its text. Any obvious reasons why?
Results Table View Controller
class ResultsVC: UITableViewController {
// data source
var stores: Results<Store> = {
let realm = try! Realm()
return realm.objects(Store.self)
}()
var token: NotificationToken?
...
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return stores.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! ResultsCustomViewCell
let stores = realm.objects(Store.self)
let currentStore = stores[indexPath.row]
cell.storeNumber.text = "#\(currentStore.storeNumber)"
cell.storeName.text = "\"\(currentStore.storeName)\""
return cell
}
}
Here is how I'm accessing the ResultsVC from MasterVC
Master View Controller
class MasterViewController: UIViewController {
...
#IBAction func searchDatabase(_ sender: Any) {
let CVC = childViewControllers.first as! UINavigationController
let resultVC = CVC.viewControllers[0] as? ResultsVC
result.stores = stores.filter("address = '1234 Blue Street'")
result.tableView.reloadData()
}
...
}
Turns out I had a duplicate variable which was overwriting the orig from above.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! ResultsCustomViewCell
let stores = realm.objects(Store.self) // <- OVERWRITING ORIGINAL //
let currentStore = stores[indexPath.row]
cell.storeNumber.text = "#\(currentStore.storeNumber)"
cell.storeName.text = "\"\(currentStore.storeName)\""
return cell
}

Master Detail View With Segue

I'm Trying to learn how to do a detail view for my project .
I have a simple tableView with a simple Array data to fill it.
The Table View :
TableView Example
I designed a detail View as well, with static tableViewCells
Detail View example :
Example
I'v Connected both with a segue :
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("Profile", sender: indexPath);
}
I also connected all the labels and images with Outlets i want to change between each cell but i don't how to advance from here.
right now every cell shows the same thing but i want to change the data between rows . So i would like to change the data through the segue and create a master detail application like in my tableview. Can anybody help me ?
Am using Swift 2.3 and Xcode 8.1
If I understand your question correctly, you just want to pass dataSource element to the next viewController. So you can just pick it using indexPath.row and use sender parameter to set it in prepareForSegue method.
The code below assumes your dataSource is self.users array.
Swift 3
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let user = self.users[indexPath.row]
self.performSegueWithIdentifier("Profile", sender: user)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let segueId = segue.identifier? else { return }
if segueId == "Profile" {
guard let profileVC = segue.destination as? ProfileViewController else { return }
profileVC.user = sender as? User
}
}
Swift 2
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let user = self.users[indexPath.row]
self.performSegueWithIdentifier("Profile", sender: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let segueId = segue.identifier else { return }
if segueId == "Profile" {
guard let profileVC = segue.destinationViewController as? ProfileViewController else { return }
profileVC.user = sender as? User
}
}
Edit
im trying to change data like al the labels you saw between rows like
for example shalvata will have a different data from light house and
so , change the labels and images and so on
It is still unclear for me what data you want to change exactly. Also I don't understand the language on your screenshots, but since you name the relationship as master-detail, I suppose the second screen is meant to show more info about the entity selected on the first screen.
If so, you should start from designing you model so that it contains all those fields you need on the second screen. Judging by the icons it would be something like
struct Person {
var name: String?
var image: UIImage?
var age: Int?
var address: String?
var phone: String?
var schedule: String?
var music: String?
var smoking: Bool?
var car: String?
var info: String?
var hobby: String?
}
Note: Remove ? for those fields which aren't optionals, i.e. always must be set for every entity (perhaps name field)
Usage
I don't known how and when you create your Person array, but basically there are two approaches:
Use a list of entities with all fields filled on MasterVC and just pass the selected person to the DetailVC in didSelectRowAtIndexPath
Use a list of entities with some basic data (name, address, image) required for MasterVC and fill the rest of the fields only when required (didSelectRowAtIndexPath method)
In any case you'll get selected person in DetailVC and now everything you need is to use that data in cellForRow method, just as you did on MasterVC. Perhaps it would be a better option to use static TableViewController for Details screen.
Sounds like what you're trying to do does not involve segues at all. You can change data of cells using the cellForRow method in your tableViewController.
https://developer.apple.com/reference/uikit/uitableview/1614983-cellforrow
For example
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "foo"
return cell
}
If that sounds confusing to you then you should take a step back and do some tutorials then post specific questions on SO.

Container View that contains a TableView change number of rows depending number of inputs Swift

I am new to swift as well as creating iOS apps and I thought I would make a simple app that calculates the averages of the numbers inputted into the TextField. The averageViewController also has a container view as well that contains TableView. Once the person has hit the "Next" button I would like the TableView to display the numbers that have been inputted. (each cell label has a single number).
This is my segue method in my averageViewController as well as the function I am using when the user presses the button:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "sendResult" {
let inputArray = segue.destinationViewController as! averageTableViewController
inputArray.arrayFromSegue = average.getArray()
}
}
#IBAction func nextButton(sender: UIButton) {
average.arrayInput(((inputTextField.text!) as NSString).doubleValue)
calcAverage()
inputTextField.text=nil
}
This is the code I made for my averageTableViewController:
class averageTableViewController: UITableViewController {
var arrayFromSegue = NSMutableArray()
var arrayUsed = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
arrayUsed = arrayFromSegue
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var Cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
Cell.textLabel?.text = String(arrayUsed[indexPath.row])
return Cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayUsed.count
}
}
I have done quite a bit of research and I believe one of my mistakes is that the action segue that I am doing (show) does not produce the correct results.
Problem isn't in segue you do. If you want change number of rows depend on number of input you should update your array data and reload your table. In your case you can change like this:
Create variable hold your tableViewController, in your case can put name is: inputArray
inputArray = segue.destinationViewController as! averageTableViewController
When you tap nextbutton you update array average and assign it to tableViewConroller `inputArray and reload it:
inputArray.arrayUsed = average.getArray()
inputArray.tableView.reloadData()
If you have any problem don't hesitate ask me. I will help you.
You can check my demo: Demo
Your project leak segue to tableviewcontroller: Please fix project like step below:
drage segue from average to averagetable
make it is embed:
Select it and name it sendResult

Resources