Using KeyPathComparator in SwiftUI with Realm Objects - sorting

I am showing a Realm object collection realmobjects = Results<Object> in a SwiftUI Table on MacOS.
Table(realmobjects, selection: $selection, sortOrder: $sorting)
How can I use the KeyPathComparator that comes from the $sorting binding to sort with the Realm objects?
As in:
realmobjects = realm.objects(Object.self).sorted(byKeyPath: "property", ascending: true)
Is it possible to use a Swift KeyPathComparator in the byKeyPath of this Realm sorted function?

Try like
#State private var sorting = [KeyPathComparator(\Object.property)]

Related

How to implement two way binding with Swift?

How to implement a two way data binding using Swift 2.0?
Let's assume I have the following model class (using couchbase lite):
#objc(Person)
class Person: NSObject{
#NSManaged var firstName: NSString?
#NSManaged var lastName: NSString?
}
And I like to bind the properties to a given formItemDescriptor like this:
class FormItemDescriptor {
var tag: String?
var value: Any?
}
How would I bind the FormItemDescriptor.value property to the Person.firstName property using 2-way-binding?
So changes in the model appear in the form and vice versa?
Swift does not support bindings out of the box, but you can achieve them with frameworks like ReactiveKit.
For example, when you declare an observable property you can bind it to another observable property.
let numberA: Property<Int>
let numberB: Property<Int>
numberA.bindTo(numberB)
To do bi-directional, just bind in other direction too:
numberB.bindTo(numberA)
Now, whenever you change any of them, the other will change.
Your example is bit harder though because you need to do bindings from #NSManaged properties that cannot be made Observable. It's not impossible, but to work the properties of Person should support key-value observing (KVO).
For example, given a Person object and a label, you could take an Property from KVO-enabled property with the method rValueForKeyPath and then bind that observable to the label, like this:
let person: Person
let nameLabel: UILabel
person.rValueForKeyPath("firstName").bindTo(nameLabel)
If you have an intermediately object like your FormItemDescriptor, then you'll have to make its properties observable.
class FormItemDescriptor {
var tag: Property<String?>
var value: Property<Any?>
}
Now you could establish binding
let person: Person
let descriptor: FormItemDescriptor
person.rValueForKeyPath("firstName").bindTo(descriptor.value)
Because firstName is not observable, you cannot do binding in another direction so you'll have to update it manually whenever value changes.
descriptor.value.observeNext { value in
person.firstName = value as? NSString
}
We also had to do cast to NSString because value is of type Any?. You should rethink that though and see if you can make it a stronger type.
Note that UIKit bindings are coming from ReactiveUIKit framework. Check our documentation of ReactiveKit for more info.

How do I query Realm database based on current user ID?

I have a Realm database which holds records for different users with distinct userID's.
I also have a UITableViewController which displays the results of a Realm query. I would like the query to return only the Passages for the current user.
class PassageListController: UITableViewController {
var currentUserID: Int = NSUserDefaults.standardUserDefaults().objectForKey("currentUserID") as! Int
lazy var dataArray: Results<Passage> = { return try! Realm().objects(Passage).filter("userID == \(currentUserID)")}
The problem is that I'm getting the following error message:
Instance member 'currentUserID' cannot be used on type 'PassageListController'
This is despite trying to lazily load the Realm query. What is the best practice method to solve this problem?
Anticipating some solutions:
I can't load the query in viewDidAppear() as there is no way to define an empty Realm Result and it needs to be accessible throughout the controller.
I could set the currentUserID as a global variable but that violates my principles.
It looks like it's having some trouble working out the computed value of currentUserID before even getting to the Realm query.
Looking around on SO, it looks like this sort of issue can be solved by changing the definition of currentUserID to a read-only accessor:
var currentUserId: Int {
get {
return NSUserDefaults.standardUserDefaults().objectForKey("currentUserID") as! Int
}
}
I hope that helped!
TiM's answer clued me in to the right approach:
class PassageListController: UITableViewController {
var currentUserID: Int = NSUserDefaults.standardUserDefaults().objectForKey("currentUserID") as! Int
var dataArray: Results<Passage> {
get {
return try! Realm().objects(Passage).filter("userID == \(currentUserID)")
}
}
The project builds now but I'm not sure whether this is the best approach with Realm because
now the query will potentially be run every time dataArray is accessed, and
I'm not sure whether Realm's notifications will work.

IOS Swift defining own app-settings in own class

im new in Swift and X-Code. Im searching for a way import a own defined class (best case in an own document). I want store all needed settings (text-snippets and app-settings) in this file. For example:
class snippetsAndSettings {
// e.g translations
let t_welcome: String = "Welcome to my App"
let t_share: String = "Social Share Buttons"
let t_rate: String = "Rate"
// e.g settings
let s_weburl: String = "http://www.mypage.com/webservice.php"
let s_slider: Bool = false
let s_bgColor: String = "#ff9900"
let s_tColor: String = "#222222"
let t_shadow: String? // Bool or colorString!
}
I want to use this class on every app-page. But i can not import the for for example in ViewController.swift
My questions:
In which format do i have to save the file (snippetsAndSettings)? Cocoa Class
How can i import the file in my ViewController.Swift
Is this a common way to store own app-settings in Swift?
The method to store settings is via NSUserDefaults. You can setup your default values in your app delegate's application:didFinishLaunchingWithOptions: method. For example:
NSUserDefaults.standardUserDefaults().registerDefaults([
"volume": 100,
"showShadows": true
])
To get these back, you would do:
NSUserDefaults.standardUserDefaults().integerForKey("volume")
and
NSUserDefaults.standardUserDefaults().boolForKey("showShadows")
In Swift 5, it now looks like this:
UserDefaults.standard.register(defaults: [
"volume": 100,
"showShadows": true
])
And to get it back
let volume = UserDefaults.standard.integer(forKey: "volume")
let showShadows = UserDefaults.standard.bool(forKey: "showShadows")
For translations, you should look in to Internationalization and Localization

How to find out the indentation level in Outline View using Swift

I am using Swift, Cocoa Bindings and Core Data in an OSX Xcode project to display an outline view which is bound to my entity "Series" and displays the attribute "name", which has a to-many relationship with itself.
I want my users to be able to enter only three levels: one root, a child and another child of that child.
Here is my subclass for Series:
#objc(Series)
class Series: NSManagedObject {
#NSManaged var isLeaf: NSNumber
#NSManaged var name: String
#NSManaged var parent: Series
#NSManaged var subGroups: NSSet
}
Effectively when the data is entered and saved, "isLeaf" should be changed to true for the third name entry. In this way, it would be impossible for the user to create a fourth entry.
I added this function to the Series subclass, so I could validate each new name entry:
func validateName(ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>,
error: NSErrorPointer) -> Bool {
if let test = ioValue.memory as? String {
if test != "" {
println("Name is \(test)")
// This is where we need to test for indentation level
}
} else {
}
return true
}
I'm pretty sure that what I need to do is test for the indentation level of the third entry and if it returns 2, then I need to change isLeaf from false to true.
Unfortunately, I do not know how to do this in practice. Does anyone have any suggestions, please?

WPF BindingListCollectionView to ListCollectionView for DataTable as ItemsSource

I want to do custom sorting on a ListView which has a DataTable as ItemsSource:
myListView.ItemsSource = (data as DataTable);
And this are the first lines of my sorting function:
DataView view = (myListView.ItemsSource as DataTable).DefaultView;
ListCollectionView coll = (ListCollectionView)CollectionViewSource.GetDefaultView(view);
The second line throws an execption like:
Unable to cast "System.Windows.Data.BindingListCollectionView" to "System.Windows.Data.ListCollectionView"
Has anyone a solution? Thx 4 answers
It returns an ICollectionView that is not a ListCollectionView. You most likely want a view on top of a view to get the features that ListCollectionView has. And since ICollectionView implements CollectionChanged, you wouldn't want to use BindingListCollectionView.
DataView view = (myListView.ItemsSource as DataTable).DefaultView;
ListCollectionView coll = new ListCollectionView(CollectionViewSource.GetDefaultView(view));
Although an alternative would be:
DataView view = (myListView.ItemsSource as DataTable).DefaultView;
BindingListCollectionView coll = new BindingListCollectionView(view);
If you only wanted only one view.
If you are binding directly to a WPF control, it is best to bind directly to it without making a BindingListCollectionView/ListCollectionView, as DefaultView already allows sorting of the DataTable.
Binding binding = new Binding() { Source = (myListView.ItemsSource as DataTable) };
this.myListView.SetBinding(myListView.ItemsSourceProperty, binding);
DataView view = (myListView.ItemsSource as DataTable).DefaultView;
view.Sort = "Age";
Hopefully Helpful,
TamusJRoyce

Resources