Working Code
Inside my tableView's didSelectRowAt IndexPath method, I have a call that updates the UserSave model, which is in the default realm. The function is structured like so:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let UserRealm = UserSave()
let realm = try! Realm()
try! realm.write {
UserRealm.articleLink = newsLink[indexPath.row]
UserRealm.articleBody = newsData[indexPath.row]
UserRealm.articleTitle = newsTitle[indexPath.row]
UserRealm.articleAuthor = newsSrc[indexPath.row]
}
performSegue(withIdentifier: "webKitSegue", sender: self)
}
When this is run, the realm updates with the new values, as it should.
Problem Code
I have a second model, UserPrefs, which is also a part of the default realm. It is called inside function exrefresh(writeToRealm: String). The function looks like this:
func exrefresh(passed: String) {
let UserRealm = UserPrefs()
let realm = try! Realm()
try! realm.write {
UserRealm.fetchUrl = passed
}
self.refreshControl!.beginRefreshing()
self.refreshControl!.sendActions(for: .valueChanged)
}
When this function runs, however, the realm maintains its default values, and does not update with the new one.
Models
// UserSave
import RealmSwift
class UserSave: Object {
#objc dynamic var articleTitle = "Default Title"
#objc dynamic var articleAuthor = "Default Author"
#objc dynamic var articleLink = "https://example.com"
#objc dynamic var articleBody = "Default Body"
}
// UserPrefs
import RealmSwift
class UserPrefs: Object {
#objc dynamic var applicationDark = false
#objc dynamic var fetchUrl = "https://example.com/"
}
The Issue
I can update the UserSave model just fine, though I am unable to change the values in UserPrefs, even though both are in the default realm. I use the same code (with the names substituted) to update both models, and only one works properly. I have the .realm file pulled up in the Realm Browser, and am able to watch as UserSave changes. I have followed the guide from realm.io, and their code only works on one model.
Thanks for any help in advance.
Your question mentions default values. Assuming the objects already exist, you would need to get that object to then be able to update it. Otherwise Realm would not know what object you're referring to.
Here's the code to write an object.
func exrefresh(passed: String) {
let UserRealm = UserPrefs()
UserRealm.fetchUrl = passed
let realm = try! Realm()
try! realm.write {
realm.add(UserRealm)
}
}
If you are going to only ever have one object of that type, then here's the code to update it.
let realm = try! Realm()
let results = realm.objects(UserPrefs.self)
let theOnlyOne = results.first!
try! realm.write {
theOnlyOne.fetchUrl = passed
}
This assumes you have proper error checking to know they object exists before updating.
Related
I have a button and below it is the table view. Table view cell has some random data.On button click I am calling the the api(function name is : api.urlRequest(userID: 80, businessUnitID: 2) ) .I have an API that has 35,0000 entries. What I want is to save that data in Realm database. The problem is that, when I am calling the save function, my UI freezes. I am appending the JSON data to Model and then saving it to database. I can get the start index and end index of the the JSON data.
What I tried was to call the API on background thread and when saving function is called, I am calling it on main thread. But this didn't worked.
class ViewController: UIViewController,getAdhocJSONDelegate{
let realm = try! Realm()
#IBOutlet weak var tableViewRef: UITableView!
var array = [NSDictionary]()
var adhocData : [AdhocModel] = []//for appending the JSON data to the model
var adhocDB : Results<AdhocDB>?// for accessing the database
let api = AdhocAPIParamteres()
var adhocJSONDatafromAPI : NSDictionary!
override func viewDidLoad() {
super.viewDidLoad()
adhocDB = realm.objects(AdhocDB.self)
}
#IBAction func buttonPressed(_ sender: Any) {
print("BUtton Tapped")
api.urlRequest(userID: 80, businessUnitID: 2)
api.delegate = self
}
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
adhocData.append(model)
print("data appended")
DispatchQueue.main.async {
self.saveToDb(data:model)
}
}
func saveToDb(data: AdhocModel) {
let adhoc = AdhocDB()
try! realm.write {
adhoc.SiteId = data.site_id
adhoc.AtmId = data.atm_id
adhoc.SiteAdress = data.site_address
realm.add(adhoc)
}
}
}
I want to save data in such a way that my UI doesn't freeze.
There are a few issues with the code and writing data to Realm on a background thread is covered in the documentation so I won't address that. Following that design pattern will correct the UI lockup.
This is another issue
func saveToDb(data: AdhocModel) {
**let adhoc = AdhocDB()**
You want to write your populated model to realm, but AdhocDB is a Results object, not a Realm model object. Additionally the realm object created in appTutorialData which is model, is passed to saveToDb, then another object is created and then populated with data from the first object. There's no reason to do that (in this code)
Assuming AdHocModel is a Realm object, this is much cleaner
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
try! realm.write {
realm.add(model)
}
}
}
You're going to want to wrap that write within a background thread (again, see the documentation) something like this
DispatchQueue(label: "background").async {
autoreleasepool {
.
.
.
try! realm.write {
realm.add(model)
}
}
}
You may ask about populating your array adhocData.append(model). We don't know what you're doing with it but if you're using it as perhaps a dataSource for a table view or some other UI element, you may want to consider using a Results object instead of an Array.
A significant advantage is, if you have 35,000 objects, that's a pretty sizable array and if you have more, it could overwhelm the device as ALL of that data is stored in memory. However, Results objects are lazily loaded so you could have a much larger dataset without overwhelming the device.
Additionally, when Realm objects are stored in an array, they 'Disconnect' from Realm and loose Realm functionality - they will not auto-update nor will changes to the actual object in Realm be reflected in that array nor can you just update the object - it doesn't appear to have a primary key.
However, if you populate a Results object with those models, they will be live updating - so if for example the atm_id changes in Realm, that object will automatically be updated. If you need to change a property you can change it directly on that object within a write transaction.
So the pattern would be to have a class var of Results and load your objects into those results within viewDidLoad. As you add more models, the results object will automatically be updated.
To keep your UI fresh, you would want to add observers (aka Notifications)to those Results so you can be notified when an object is updated so you can reload your tableView for example.
I have just started coding. I am making an iOS app with Swift that needs to be able to store two variables even when the app is exited. For this, I am using Realm. I am able to write those two variables, but when I try to read them, I cannot build my project.
I have tried many tutorats, but failed each time I tried to apply them to my problem.
Here is my Realm class:
class Info: Object {
dynamic var name = ""
dynamic var gender = ""
}
How can I read the value of these two variables in my ViewController?
When I try to read the variable, I have an error on the line
guard let info = realm.objects(Info.self).first else {return}
of my ViewController:
import UIKit
import RealmSwift
class SettingsViewController: UIViewController {
let realm = try! Realm()
guard let info = realm.objects(Info.self).first else {return}
let name = info.name
let gender = info.gender
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You can access Realm variables like this:
let realm = try! Realm()
guard let info = realm.objects(Info.self).first else {return}
let name = info.name
let gender = info.gender
However, if you don't plan on storing more complex data on storage, I would suggest you just use UserDefaults, for such a simple task it is easier to use.
You should also change the « » to proper " signs if you want to store String literals.
For the edited part of your question:
guard let info = realm.objects(Info.self).first else {return} has to happen inside your viewDidLoad function. You cannot access realm, which is an instance property of your class, before your class is actually initialised, hence the error you get. If you need info to be an instance property, initialise it as an optional and access it inside viewDidLoad.
First option - if you only need access to info inside viewDidLoad:
class SettingsViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
guard let info = realm.objects(Info.self).first else {return}
let name = info.name
let gender = info.gender
}
}
Second option - if you need to access info everywhere in your class instance:
class SettingsViewController: UIViewController {
let realm = try! Realm()
var info: Info?
override func viewDidLoad() {
super.viewDidLoad()
info = realm.objects(Info.self).first else {return}
let name = info.name
let gender = info.gender
}
}
If this is your initial view controller, declaring realm as an instance property might prove to be problematic, since your class might be instantiated before your application(didFinishLaunchingWithOptions) function finished execution and hence your realm schema won't be set up properly by the time you try to access it in SettingsViewController. If this is the case, just move the line let realm = try! Realm() inside viewDidLoad.
the Problem is, that I can add relationships to my object and it works temporally but realm doesn't save the changes and when I quit the app and restart ist, the relationships are gone.
In the first view I create the main object. Here is the class:
class UserListClass: Object {
dynamic var UserListName = ""
let Medis = List<MediClass>()
override static func primaryKey() -> String? {
return "UserListName"
}
}
And the code how I create the object:
let realm = try! Realm()
let newList = UserListClass ()
newList.UserListName = textField.text!
try! realm.write {
realm.add(newList)
}
This works without problems, and the object I add is also there when I restart the app.
When the user click on Edit it will open the next view:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showUserlistEdit" {
let editViewController = segue.destinationViewController as! MediUserlistEditViewController
editViewController.userlist = userlist
}
}
In the next view the code add selected entries to the list, I tried two versions, but both have the result, that it saves the relationships only for the runtime of the app. When I restart it, they are away.
First try:
let realm = try! Realm()
try! realm.write {
userlist.Medis.append(medi)
realm.add(userlist, update: true)
}
Second try:
let realm = try! Realm()
try! realm.write {
userlist.Medis.append(medi)
}
The problem is the
let realm = try! Realm()
The documentation specify
// Get the default Realm
let realm = try! Realm()
// You only need to do this once (per thread)
You did on both view controllers and there is no guarantee that the path to the default Realm will be the same. It might work on the actual device but not on simulator.
Try this and delete all other occurrences
// Put this inside your Real class Definition/Declaration file
let realm = try! Realm()
Hope this help
I'm quite familiar with Sqlite, but decided to try using realm for my next project.
I'm having trouble with reading data from the db and deleting objects
as well.
I'm using the default realm path:
let realm = RLMRealm.defaultRealm()
When a button is pressed an RLMObject should either be added or deleted (if already there). This is the IBAction for the button:
#IBAction func addToFavor(sender: UIBarButtonItem) {
// Create RealmTV (RLMObject)
let tvShow = RealmTV(id: id, title: TitleLabel.text!, posterPath: posterUrl)
if favoriteButton.image!.isEqual(UIImage(named: "Favor unfilled")) {
realm.beginWriteTransaction()
// Create or update tv-show in database
RealmTV.createOrUpdateInDefaultRealmWithValue(tvShow)
try! realm.commitWriteTransaction()
// Change button state
favoriteButton.image = UIImage(named: "Favor filled")
}
else
{
realm.beginWriteTransaction()
// Delete tv-show object from database
realm.deleteObject(tvShow) /* RLMException here */
try! realm.commitWriteTransaction()
// Change button state
favoriteButton.image = UIImage(named: "Favor unfilled")
}
}
When I try to delete the object after it has been added to db. I get an RLMExecption saying:
'Can only delete an object from the Realm it belongs to.'
I understand what the above reason mean, but not how to solve it?
And also how do I retrieve only this object from db after it has been added?
EDIT
This is my RealmTv class:
import UIKit
import Realm
class RealmTV: RLMObject {
dynamic var id = ""
dynamic var title = ""
dynamic var posterPath = ""
override class func primaryKey() -> String? {
return "id"
}
override init() {
super.init()
}
init(id: String, title: String, posterPath: String) {
super.init()
self.id = id
self.title = title
self.posterPath = posterPath
}
}
What the error message is trying to convey is that the object you pass to -[RLMRealm deleteObject:] must belong to the Realm that you're trying to delete the object from. In your case you're passing a new object that does not belong to any Realm (such an object is referred to as a standalone or unpersisted object in Realm's documentation). Instead you must pass either an object that you have retrieved from the Realm (using -[RLMRealm objectForPrimaryKey:], +[RLMObject allObjectsInRealm:], etc.), or added to the Realm (using -[RLMRealm addObject:]).
Reworking your code to meet these requirements would look something like:
if favoriteButton.image!.isEqual(UIImage(named: "Favor unfilled")) {
realm.beginWriteTransaction()
// Create or update tv-show in database
let tvShow = RealmTV(id: id, title: TitleLabel.text!, posterPath: posterUrl)
RealmTV.createOrUpdateInDefaultRealmWithValue(tvShow)
try! realm.commitWriteTransaction()
// Change button state
favoriteButton.image = UIImage(named: "Favor filled")
}
else {
realm.beginWriteTransaction()
// Delete tv-show object from database
let tvShow = RealmTV.objectForPrimaryKey(id)
realm.deleteObject(tvShow)
try! realm.commitWriteTransaction()
// Change button state
favoriteButton.image = UIImage(named: "Favor unfilled")
}
I've got an app that I've started adding Realm to and I think I must be doing something wrong because I keep running into EXC_BAD_ACCESS when passing unpersisted objects between view controllers.
Here's a stripped down version of my app.
class TodoTask: Object {
dynamic var name: String = ""
let steps = List<Step>()
convenience init(name: String) {
self.init()
self.name = name
}
}
class Step: Object {
dynamic var name: String = ""
convenience init(name: String) {
self.init()
self.name = name
}
}
class TodoListController: UIViewController {
let todos = List<TodoTask>()
override func viewDidLoad() {
super.viewDidLoad()
var t1 = TodoTask(name: "Todo1")
let steps = [
Step("Do this"),
Step("Do that"),
]
t1.steps.appendContentsOf(steps)
var t2 = TodoTask(name: "Todo2")
let steps = [
Step("Do these"),
Step("Do those"),
]
t2.steps.appendContentsOf(steps)
todos.appendContentsOf([t1, t2])
// Display table of todos.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let detailsController = segue.destinationViewController as? TodoDetailViewController,
let selectedTask = getSelectedTask() {
detailsController.task = selectedTask
}
}
}
class TodoDetailViewController: UIViewController {
var task: TodoTask? // <<< EXC_BAD_ACCESS
// Display the task's steps.
}
Unfortunately, I can't figure out what triggers the EXC_BAD_ACCESS and it happens intermittently. I didn't copy a stacktrace (d'oh!) but I remember it being in the C++ destructor for some sort of Row object. This is odd to me because there doesn't seem to be a database file in my Documents folder.
I'm pretty confident this is a Realm-based error because I experienced no weird crashes until I converted my plain Swift objects to Realm objects.
My only hunch is that I might be using List wrong since the warning This method can only be called during a write transaction. is in the comments of the appendContentsOf method. However, I was under the impression that I could use Realm objects that weren't stored in a Realm just like normal Swift objects.
Am I using Realm objects wrong? Is there anything else I can look into?
I'm on Realm 0.95.2.
Thanks!
Edit:
func getSelectedTask() -> TodoTask? {
if let index = taskTableView.indexPathForSelectedRow {
return tasks[index]
}
return nil
}
The issue is that a Realm List should not be created directly. This class is only used to manage to-many relationships on Object models. Instead, you should use a standard Swift array to store the unpersisted Realm Objects.
So switch:
let todos = List<TodoTask>()
to
let todos = [TodoTask]()