How to implement two way binding with Swift? - swift2

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.

Related

Microsoft Bot: Show options from database instead of enums

In the example bot implementations from Microsoft, they use enums to define options for dialog, as shown in the example below:
public enum LengthOptions { SixInch, FootLong };
public enum BreadOptions { NineGrainWheat, NineGrainHoneyOat, Italian, ItalianHerbsAndCheese, Flatbread };
Can we use a normal list to fetch the values from the database and display it as options?
Thanks
You can't do this out of the box, but you could subclass FormBuilderBase<T>, overriding various methods to build the Form using whatever datasource you prefer.
Edit:
You can find the base class and implementation of FormBuilder here: https://github.com/Microsoft/BotBuilder/blob/master/CSharp/Library/FormFlow/FormBuilder.cs
Basically, there are a mess of virtual methods that you can override to customize how you want to form to behave, but the main one is Build. In the default implementation, it iterates though the enums to create a list of Field, which are basically each step in you form. Instead of that, you can iterate through whatever data you have pulled from your database and create a new Field for each item. It may look something like this:
public override IForm<T> Build(Assembly resourceAssembly = null, string resourceName = null)
{
var list = GetListOfItemsFromDatabase();
foreach (var item in _list)
{
// FieldFromItem is an IField and will also need to be created
Field(new FieldFormItem<T>(item));
}
Confirm(new PromptAttribute(_form.Configuration.Template(TemplateUsage.Confirmation)));
}
return base.Build(resourceAssembly, resourceName);
}
I know its late but found myself struggling with the same and found that below would be the right solution for this.In your FormFlow class just add the Terms and Descriptions manually.From your example if we are talking about length options then change the type of LengthOptions to string add following code when you build the form.
return new FormBuilder<SandwichForm>()
.Field(new FieldReflector<SandwichForm>(nameof(LengthOptions))
.SetDefine(async (state, field) =>
{
// Call database and get options and iterate over the options
field
.AddDescription("SixInch","Six Inch")
.AddTerms("SixInch", "Six Inch")
.AddDescription("FootLong ","Foot Long")
.AddTerms("FootLong ", "Foot Long")
return true;
}))
.OnCompletion(completionDelegate)
.Build();

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.

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?

Set value in model when dynamically creating object of model

I have a bunch of models that may or may not have a property "CommonProperty". I want to set that property when I am creating a new object of a selected model. What I have so far, which works is:
ModuleItem model = db.ModuleItems.Find(ModuleItemID);
object o = GetModuleType(model.ControllerName);
private object GetModuleType(string ModelName)
{
string projectName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
Type classtype = Type.GetType(string.Format("{0}.Models.{1}", projectName, ModelName));
PropertyInfo[] properties = classtype.GetProperties();
var classObject = classtype.GetConstructor(new Type[] { }).Invoke(null);
return classObject;
}
What I want to achieve is to set CommonProperty:
o.CommonProperty = DistinctValue;
I tried creating a class that all of my models inherit from with a virtual method and each model then has an override method. Because its not static I can't call it directly and if I create a new ModelBase then when calling the method it doesn't get overriden by the type that object "o" is. I looked at creating an interface but 1 I don't even know how these work, and 2 (probably because of 1) I am not even able to create a way of doing this without build errors.
When stepping through the code I can see all the properties of "o" using the quickwatch or intellisense or whatever it's called. Clearly it is being recognised as the correct type. I can't seem to be able to call the method though because it's not a recognised (or set) type during build only during runtime and therefore I can't build the solution.
What I would do is create a base model (eg: BaseModel) for viewmodels that have the property CommonProperty then check whether the model is of type BaseModel then set the property
if (obj is BaseModel)
{
obj.CommonProperty = DistinctValue;
}
As it turns out I was way overthinking this. Typically the answer is ridiculously simple. No base model necessary, no overrides, no virtuals, no instances, dump it in a try catch and just set the value of the context object.
object o = GetModuleType(model.ControllerName);
db.Entry(o).State = EntityState.Added;
try
{
db.Entry(o).CurrentValues["CommonProperty"] = DistinctValue;
}
catch (Exception err) { }
I did have to make sure I set the state BEFORE setting a current value otherwise it wasn't in the context (or something like that).

When selecting an anonymous type with LINQ from EF, is there no way to run a method on an object as you select it?

Let's say I have a method:
bool myMethod(int a)
{
//return a bool
}
So let's say I the following
// assume a has prop1 and prop2 both ints
var mySelection = from a in myContainer
where a=somecondition
select new {
a.prop1,
myMethod(a.prop2)
};
Is there really no way to run myMethod in the anonymous type declaration? Is there some sort of trick?
Can I put an anonymous method in there to return the equivalent of myMethod(a.prop2)?
Well lets separate this into LINQ to Objects and LINQ to Entities
In LINQ to Object the above fails because the compiler doesn't know what the Property name is, if you change it to this:
var mySelection = from a in myContainer
where a=somecondition
select new {
a.prop1,
prop2 = myMethod(a.prop2)
};
It will work in LINQ to Objects
However the Entity Framework won't be able to translate the method call (unless it is a function known to the EF like a Model Defined Function, EdmMethods or SqlMethods) so you'll have to rewrite that query like this:
var mySelection = from a in myContainer
where a=somecondition
select new {
a.prop1,
a.prop2
};
var myResults = from a in mySelection.AsEnumerable()
select new {a.prop1, prop2 = myMethod(a.prop2)};
This pulls what you need out the database, and then using the AsEnumerable() call turns the call to myMethod into something processed by LINQ to Objects rather than LINQ to Entities
Hope this helps
Alex
I don't think there is a way to call out to a method from an anonymous initializer. Even if there were, it probably wouldn't perform very well.
If you need to create a result set that requires additional processing, I would create a concrete class.

Resources