Realm, best approach to save\update\Observe String to Realm Object - swift

(looking for the best approach to save Realm property.
I have a UIViewController with a lot of TextView, etc that I fill from a Realm object.
Each time the textfield are modified, I need to send back to change un the realm property.
The (not cool) thing, are that I cannot save directly, I have to open a write transaction.
object.propertyA= “hello” // crash
try! realm.write { //work
userBeer?.Name = lblbeerName.text!
}
So, i found a bit painfull (and not clean) to to that for all text.
I’ve looked at rxRealm, but cannot see any (newbies) sample to make that.
So, I have 2 approach un mind
Modify the model getters and setters for the property
var beerName: String? {
get {
return self.Name
}
set {
try! realm.write {
self.txtName=beerName!
}
}
use the RXSwift approach from here (https://www.raywenderlich.com/149753/bond-tutorial-bindings-swift)
Bing the TextField.text to a var String, and observe this string to write.
What do you think?
My perfect world will be to find a way to bing the TextField.text property directly, something like:
myRealmObject.property.BindTo(self.txtName)

Katsumi from Realm here. Although it is not the best approach, I propose another way. You can use realm.beginWrite() and try! realm.commitWrite() instead of block-based API for a long-lived transaction.
For example, you can open a transaction when the view appeared, and then close the transaction when the view disappeared, like the following:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
realm.beginWrite()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
try! realm.commitWrite()
}
In this way, you can assign a value to the Realm object at any time in the view.
Be careful not to leave the transaction open. To avoid increasing file size, do not update data frequently in the background while transactions are open.

Change the property within the write block:
try! realm.write {
object.propertyA = “hello”
userBeer?.Name = lblbeerName.text!
}

Related

Prevent/Postpone tableview updates from NSFetchedResultsController when table is not visible

Xcode 11.0
Swift 5.1
I'm working on a "simple" app using Core Data.
The single Core Data entity is named Topic and comprises Title (String), Details (String) and isFavorite (Bool)
I'm using a custom UITabBarController with 3 tabs - Random Topic, Favorite Topics, and All Topics
Favorite Topics and All Topics are UITableViews sharing the same subClass, TopicsViewController.
Using custom TopicTabBarController I set a property in TopicsViewController, based on the tab, that determines whether or not a predicate is used with the FetchRequest for the two tabs sharing this controller.
This works as expected, even with FRC cache (I'm very new to Swift!)
If a topic is favorited on any view, the tableViews in both Favorite Topics and All Topics update to reflect that change. This is precisely how I want it.
The trouble is I'm getting a warning about the tableView being updated while not visible:
UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window).
I set a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy but making sense of that is a bit above my head at the moment.
I tried returning early from the FRC Delegate methods if the view wasn't loaded but that was clearly not the solution. Like I said, this is all new to me.
I thought setting the FRC Delegate to nil and back to self as suggested in this post would help, but that only prevents the managedObjectContext from saving (at least that's what I'm seeing with Navicat). I can step through the code and see that the proper instances of the controller class are being manipulated based on the custom property I set for the predicate.
Here's the related code for that part:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.fetchedResultsController.delegate = self
self.performFetch()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.fetchedResultsController.delegate = nil
}
// MARK:- Helper methods
func performFetch() {
do {
try fetchedResultsController.performFetch()
} catch {
fatalCoreDataError(error)
}
}
Here's where the entity is saved, which is not happening with the above code in place:
#objc func toggleFavorite(_ sender: UIButton) {
let buttonPosition = sender.convert(sender.bounds.origin, to: tableView)
if let indexPath = tableView.indexPathForRow(at: buttonPosition) {
let topic = fetchedResultsController.object(at: indexPath)
topic.isFavorite = !topic.isFavorite
try! managedContext.save()
}
}
Here's a Gist with more details. I find it much easier to read this way.
I also just came across this post on the Apple Dev forum. Looks like the same issue, unresolved.
I honestly don't know if that's too much or too little information. Was really hoping I'd stumble on the solution while trying to explain the problem.
Thanks for your time.
For what it's worth, I was getting the same warning and I was able to fix it by doing something very similar to what you tried. Basically the only difference is the tableView.reloadData().
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fetchedResultsController.delegate = self
tableView.reloadData()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
fetchedResultsController.delegate = nil
}
... though I would think that performing a fetch would work just the same?

adding the duplicate data to the array: error

my problem is adding the duplicate data to the array
my program it works well before it can be refresh manually but duplicate added to list when manually refreshed
when I check the print, the data is added to the double list
print result
ARRAYLAR : ["EXAMPLE", "EXAMPLE"]
Watch the video for a better understanding of the problem
VİDEO
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getData()
}
#objc func getData() {
self.konuAdiArray.removeAll(keepingCapacity: false)
self.konuHedefTarihArray.removeAll(keepingCapacity: false)
self.konuTestArray.removeAll(keepingCapacity: false)
self.konuIDArray.removeAll(keepingCapacity: false)
self.veriGirisArray.removeAll(keepingCapacity: false)
Database.database().reference().child("users").child((Auth.auth().currentUser?.uid)!).child("dersler").child(gelenDersID!).child("konular").observe(DataEventType.childAdded) { (snapshot) in
let values = snapshot.value! as! NSDictionary
self.konuAdiArray.append(values["konuAdi"]as! String)
self.konuHedefTarihArray.append(values["konuHedefTarihi"]as! String)
self.konuTestArray.append(values["konuTestHedefi"]as! String)
self.veriGirisArray.append(values["veriGirisSoru"]as! String)
self.konuIDArray.append(snapshot.key)
print("ARRAYLAR : \(self.konuAdiArray)")
self.tableView.reloadData()
}
}
refreshBarButton code
#IBAction func refreshBarButton(_ sender: Any) {
getData()
}
You need to make separate function for observing changes of your data base. When you call getData() you code calls twice of DB changes. So that you subscribe on changes one more time that's why I'd recommend you to make separate function like setDataBaseObserver() which you call only one time in viewDidLoad. If you are updating your data you should make network request or take them from different source (not from data base again). Hope you understand me right!
func setDataBaseObserver() {
Database.database().reference().child("users").child((Auth.auth().currentUser?.uid)!).child("dersler").child(gelenDersID!).child("konular").observe(DataEventType.childAdded) { (snapshot) in
let values = snapshot.value! as! NSDictionary
self.konuAdiArray.append(values["konuAdi"]as! String)
self.konuHedefTarihArray.append(values["konuHedefTarihi"]as! String)
self.konuTestArray.append(values["konuTestHedefi"]as! String)
self.veriGirisArray.append(values["veriGirisSoru"]as! String)
self.konuIDArray.append(snapshot.key)
print("ARRAYLAR : \(self.konuAdiArray)")
self.tableView.reloadData()
}
}
Hope it will help to you!
There's no reason for the manual refresh. You are observing childAdded on a Firebase database, which will continue to update in real time. Whenever you hit the manual refresh, your getData() is adding your controller as an observer again. Either remove the manual refresh control and just let Firebase do its thing (this is what it excels at - realtime updates without manual refresh), or change your childAdded observation to be a one-time data fetch.

Way to check data modification across many viewcontroller without using global variables?

I have an app which contains several viewControllers. On the viewDidAppear() of the first VC I call a set of functions which populate some arrays with information pulled from a database and then reload table data for a tableView. The functions all work perfectly fine and the desired result is achieved every time. What I am concerned about is how often viewDidAppear() is called. I do not think (unless I am wrong) it is a good idea for the refreshing functions to be automatically called and reload all of the data every time the view appears. I cannot put it into the viewDidLoad() because the tableView is part of a tab bar and if there are some modifications done to the data in any of the other tabs, the viewDidLoad() will not be called when tabbing back over and it would need to reload at this point (as modifications were made). I thought to use a set of variables to check if any modifications were done to the data from any of the other viewControllers to then conditionally tell the VDA to run or not. Generally:
override func viewDidAppear(_ animated: Bool) {
if condition {
//run functions
} else{
//don't run functions
}
}
The issue with this is that the data can be modified from many different viewControllers which may not segue back to the one of interest for the viewDidAppear() (so using a prepareForSegue wouldn't work necessarily). What is the best way to 'check' if the data has been modified. Again, I figured a set of bool variables would work well, but I want to stay away from using too many global variables. Any ideas?
Notification Center
struct NotificationName {
static let MyNotificationName = "kMyNotificationName"
}
class First {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(self.notificationReceived), name: NotificationName.MyNotificationName, object: nil)
}
func notificationReceived() {
// Refresh table view here
}
}
class Second {
func postNotification() {
NotificationCenter.default.post(name: NotificationName.MyNotificationName, object: nil)
}
}
Once postNotification is called, the function notificationReceived in class First will be called.
Create a common global data store and let all the view controllers get their data from there. This is essentially a global singleton with some accompanying functions. I know you wanted to do this without global variables but I think you should consider this.
Create a class to contain the data. Also let it be able to reload the data.
class MyData {
static let shared = MyData()
var data : SomeDataType
func loadData() {
// Load the data
}
}
Register to receive the notification as follows:
static let dataChangedNotification = Notification.Name("DataChanged")
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
// Establish a way for call activity to notify this class so it can update accordingly
NotificationCenter.default.addObserver(self, selector: #selector(handleDataChangedNotification(notification:)), name: "DataChanged", object: nil)
}
func handleDataChangedNotification(notification: NSNotification) {
// This ViewController was notified that data was changed
// Do something
}
func getDataToDisplay() {
let currentData = MyData.shared.data
// do something
}
// Any view controller would call this function if it changes the data
func sendDataChangeNotification() {
let obj = [String]() // make some obj to send. Pass whatever custom data you need to send
NotificationCenter.default.post(name: type(of: self).dataChangedNotification, object: obj)
}

Swift Storing information in a constant and keeping it there

Hello I am currently doing a project where the user types something in a textfield, and presses a button, where whatever the user types in the textfield appears as part of the alertbody of a notification.
To do this I save the textfield.text as a variable and then use string interpolation to include it into the alertbody.
The problem is that the time in which I fire the notifications change every week, so I have to make new notifications. And I still want to use whatever the user typed into the textfield. But since the textfield.text was saved inside a function, I do not think that it will be stored outside the function.
This is what my code looks like so far
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer.scheduledTimerWithTimeInterval(604800, target: self,selector:Selector("repeater"), userInfo: nil, repeats: true)
}
savedtext:String!
#IBAction func setNotification{
textfield.text = savedtext
//set notifications and other stuff here
}
//outside the function savedtext should not have the textfield's info
func repeater{
//setting notification
}
Sorry if my question is a bit difficult to understand
Any help is appreciated
I'd use NSUserDefaults here, it will persist your data in a key-value map, even after the user or system kills the app. The code would look like this
let ALERT_MESSAGE_KEY : String = "myAlertMsgKey"
#IBAction func setNotification{
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue(textfield.text , forKey: ALERT_MESSAGE_KEY)
NSUserDefaults.standardUserDefaults().synchronize()
//set notifications and other stuff here
}
and in repeater function:
func repeater {
let defaults = NSUserDefaults.standardUserDefaults()
let alertMessage = defaults.valueForKey(ALERT_MESSAGE_KEY) as? String ?? "No message saved"
}
You could also use Core Data for this, but it would be an overkill for something so simple

How to use the delegates with NSKeyedUnarchiver?

I am using NSKeyedUnarchiver to unarchive an object and would like to use the delegates (NSKeyedUnarchiverDelegate), but my delegates are not called. Archiving and Unarchiving is working fine, but the Delegates (unarchiver & unarchiverDidFinish) are not called. Can someone help?
I have the following implementation:
class BlobHandler: NSObject , NSKeyedUnarchiverDelegate{
func load() -> MYOBJECTCLASS{
let data:NSData? = getBlob();
var mykeyedunarchiver:NSKeyedUnarchiver=NSKeyedUnarchiver(forReadingWithData: data!);
mykeyedunarchiver.delegate = self;
let temp=mykeyedunarchiver.decodeObjectForKey("rootobject")
// No delegates are called
if temp==nil {
blobsexists=false;
}else{
objectreturn = temp! as! MYOBJECTCLASS;
return objectreturn;
}
}
func save1(myobject:MYOBJECTCLASS){
let data = NSMutableData()
var keyedarchiver:NSKeyedArchiver=NSKeyedArchiver(forWritingWithMutableData: data);
keyedarchiver.encodeObject(maptheme, forKey: "rootobject");
let bytes = data.bytes;
let len=data.length;
saveblob(bytes);
}
The following delegates, which are also implemented in my Blobhandler, are never called:
func unarchiver(unarchiver: NSKeyedUnarchiver, cannotDecodeObjectOfClassName name: String, originalClasses classNames: [String]) -> AnyClass? {
print("I am in unarchiver !");
return nil;
}
func unarchiverDidFinish(_ unarchiver: NSKeyedUnarchiver){
print("I am in unarchiverDidFinish ! ");
}
I don't know what it was, but its working after a clean and rebuild of the project.
I notice with different cases, that the builds are not in sync sometimes. There is sometimes code, which is in XCode but it is not executed. Sounds unbelievable, but I guess its true.
XCode 7.2
I think the first function is never called since you didn't actually feed a "cannotDecodeObjectOfClassName" at all, since you only did try to unarchive previously archived data. You can try this method(or something requires a class name) to validate your solution(feed a class doesn't conform NSCoding):
unarchiver.decodeObjectOfClass(cls: NSCoding.Protocol, forKey: String)
The second one is a little bit tricky. I've tried this method in a similar situation and it turned out that unarchiverDidFinish only get called when a complete unarchiving job is done and probably before it's destroyed. For example, I had a NSCoding class and the convenience initiator is like
required convenience init?(coder aDecoder: NSCoder) {
let unarchiver = aDecoder as! NSKeyedUnarchiver
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
unarchiver.delegate = appDelegate.uad
let name = unarchiver.decodeObjectForKey(PropertyKey.nameKey) as! String
print(321)
self.init(name: name, photo: photo, rating: rating)
}
uad is an instance of class:
class UAD:NSObject, NSKeyedUnarchiverDelegate {
func unarchiverDidFinish(unarchiver: NSKeyedUnarchiver) {
print(123)
}
}
And in the view controller the loading process is like
func load() -> [User]? {
print(1)
let ret = NSKeyedUnarchiver.unarchiveObjectWithFile(ArchiveURL.path!) as? [User]
print(2)
return ret
}
And the output is like:
1
321
321
321
321
321
123
2
After finishing loading a group of users, the unarchiverDidFinish finally got called once. Notice that this is a class function and an anonymous instance is created to finish this sentence:
NSKeyedUnarchiver.unarchiveObjectWithFile(ArchiveURL.path!) as? [User]
So I really believe that this function only get called before it is destroyed or a group of call back functions is finished.
I am not quite sure if this is the case for you. You may try to make your unarchiver object global and destroy it after your loading is done to see whether this function is called.
Correct me if anything not right.
To make either unarchiverWillFinish: and unarchiverDidFinish: be called properly, we have to invoke finishDecoding when finished decoding.
Once you have the configured decoder object, to decode an object or data item, use the decodeObjectForKey: method. When finished decoding a keyed archive, you should invoke finishDecoding before releasing the unarchiver.
We notify the delegate of the instance of NSKeyedUnarchiver and perform any final operations on the archive through invoking this method. And once this method is invoked, according to Apple's official documentation, our unarchiver cannot decode any further values. We would get following message if we continue to perform any decoding operation after invoked finishDecoding:
*** -[NSKeyedUnarchiver decodeObjectForKey:]: unarchive already finished, cannot decode anything more
It also makes sense for encoding counterparts.