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

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?

Related

SwiftUI's Table: can you disable content diffing to improve performance?

I'm using SwiftUIs 'Table' to display large arrays (> 100.000) but experiencing nearly O(n²) performance penalty on updates of the array. In the following example (macOS) the table is drawn instantly on app start, but replacing the contents of the 'items' array takes several seconds, even if the new array is just empty:
struct TableTest: View {
#State private var items: [Person] = (0..<500000).map { _ in Person() }
var body: some View {
Table(items) {
TableColumn("Name", value: \.name)
TableColumn("Age") { person in Text(String(person.age)) }
}
.toolbar {
ToolbarItem() {
Button("Clear") {
items = []
}
}
}
}
struct Person: Identifiable {
let id = UUID()
let name: String = ["Olivia", "Elijah", "Leilani", "Gabriel", "Eleanor", "Sebastian", "Zoey", "Muhammad"].randomElement()!
let age: Int = Int.random(in: 18...110)
}
}
An array size of 1.000.000 items takes >30s to update.
Profiling the app suggests that the heavy lifting happens in
v AppKitTableCoordinator.updateTableView(_:from:to:)
> ListBatchUpdates.computeMoves<A>(from:to:)
> ListBatchUpdates.computeRemovesAndInserts<A>(from:to:)
so I assume the delay is caused by automatic diffing of the old and new array. I can see that Table accesses each and every of my 500.000 'Person' structs individually asking their 'id' which also suggests that it's diffing the arrays.
It would be great to switch that off. I tried using the .id(UUID()) modifier on the Table to force SwiftUI into treating each Table struct as brand new, but that didn't change anything.
The only workaround I see is using 'NSViewRepresentable' to display an 'NSTableView' but I'd prefer to use 'Table'.
So my question: is there a way to convince SwiftUI to refrain from diffing?

SwiftUI List .onDelete not working with filtered Core Data

I have a SwiftUI application that is basically a master/detail app with data persisted in Core Data. The basic application works perfectly, however I have added a search bar (through UIViewRepresentable) and I had difficulty with the ForEach.onDelete functionality when the list is filtered.
If there is text in the search bar, I execute a fetch request on the Core Data entity which includes a predicate to search all of the text fields that I want to include. Then the filtered list is presented. Again, this works as expected. However, attempting to delete a record causes a crash. I believe this is because the IndexSet becomes invalid but I don't understand why that should be. At any rate, if I re-issue the call
to the filtered list in the .onDelete code (see the first .onDelete below), then the deletions work as expected. Even with this secondary search however, changes made on the detail screen are not shown when returning to the filtered list, though the changes are saved in Core Data.
While I guess it is not a big penalty to have to re-fetch, I am not confident that this is the proper procedure, and changes do not cause the List to update. Is there a way to use the #FetchRequest wrapper and dynamically provide a predicate? I have seen several SO posts on dynamic filters in Core Data but none of them have worked for me.
Any guidance would be appreciated.
struct ContentView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: Patient.getAllPatients()) var patients: FetchedResults<Patient>
#State private var searchTerm: String = ""
#State var myFilteredPatients = Patient.filterForSearchPatients(searchText: "")
//more properties
var body: some View {
NavigationView {
List {
MySearchBar(sText: $searchTerm)
if !searchTerm.isEmpty {
ForEach(Patient.filterForSearchPatients(searchText: searchTerm)) { patient in
NavigationLink(destination: EditPatient(patient: patient, photoStore: self.photoStore, myTextViews: MyTextViews())) {
//configure the cell etc.
}//NavigationLink
}//for each
.onDelete { indexSet in
//This recreation was necessary:
self.myFilteredPatients = Patient.filterForSearchPatients(searchText: self.searchTerm)
let deleteItem = self.myFilteredPatients[indexSet.first!]
self.managedObjectContext.delete(deleteItem)
do {
try self.managedObjectContext.save()
} catch {
print(error)//put up an alert here
}
}
} else {
ForEach(self.patients) { patient in
NavigationLink(destination: EditPatient(patient: patient, photoStore: self.photoStore, myTextViews: MyTextViews())) {
//configure the cell etc
}//nav link
}//for each
.onDelete { indexSet in
let deleteItem = self.patients[indexSet.first!]
self.managedObjectContext.delete(deleteItem)
do {
try self.managedObjectContext.save()
} catch {
print(error)//put up and alert here
}
}
}//if else
}//List
//buttons and setup
}//NavigationView
}//body
Xcode Version 11.2 (11B52)

Not able to extract particular field from "MBRecognitionResult*" in BlinkIDUI-iOS sdk?

I am using your new BlinkIDUI sdk for iOS and I can have the list of all the scanned fields from "recognitionResult.resultEntries" like Secondary ID = Jason", "Primary ID = Bourne", "Sex = F", "Date Of Birth = 3/23/83", "Nationality = UAE", "Document Code = P" from the delegate method "- (void)didScanEntireDocumentWithRecognitionResult:(MBRecognitionResult * _Nonnull)recognitionResult successFrame:(UIImage * _Nullable)successFrame". My query is How to get value for particular key like “"Document Code” ?
Additional Details are:
The Framework addition in Project: Manual.
Xcode version : 10.1.
Language: Objective-C (ARC OFF).
Device: iPhone8 / iOS(11.1.1)
That's because resultEntries is an array not a dictionary,
Use like this:
for (MBField *field in recognitionResult.resultEntries) {
if (field.key == MBFieldKeyDocumentCode) {
}
}
If you are using it in ObjectiveC project then also check if #objc tag is there in front of MBFieldKey public property in "MBField" class, if it is not there just put it as:
public class MBField: NSObject {
#objc public let key: MBFieldKey
#objc public let value: String
.....
}

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 to make NSTextField only editable if exactly one item is selected in NSTableView?

In a master-detail application, my master table view allows multiple selections. I use NSArrayController to populate the table view.
I want the text fields in the detail view only be editable, when exactly one item in the master table view is selected.
Disabling "Allows Editing Multiple Values Selection" in the text field's binding is not enough, because it only disables editing, when the multiple selected items have different values. I want editing disabled always, as soon as more than one item is selected.
Is this achievable from within interface builder?
One option is to bind the Editable state of your NSTextField instances to the selectionIndexes property of your NSArrayController, then to use a custom value transformer to convert the associated NSIndexSet to a boolean whose value is determined by the number of indexes in the index set.
The Interface Builder set-up would look like this:
The value transformer subclass would look like this:
#objc(PPSelectionIndexesCountIsExactlyOneTransformer)
public class PPSelectionIndexesCountIsExactlyOneTransformer: NSValueTransformer {
override public class func allowsReverseTransformation() -> Bool {
return false
}
override public class func transformedValueClass() -> AnyClass {
return NSNumber.self
}
override public func transformedValue(value: AnyObject?) -> AnyObject? {
var retval: AnyObject?
if let indexSet = value as? NSIndexSet {
retval = NSNumber(bool: indexSet.count == 1)
}
return retval
}
}
I found that a combination of
Selecting "Always Use Multi Value Marker" on the NSArrayController and
Deselecting "Allows Editing Multiple Value Selection" on the NSTextField's value binding
results in the behaviour I was looking for.

Resources