Let's say you have a custom control similar to NSSlider but with support for choosing a range of values, instead of a single value. The obvious choice of properties is minValue, maxValue, leftValue, rightValue, or however you wanna name them.
You'd probably also want to make sure that leftValue and rightValue always lay in between minValue and maxValue. Same for minValue and maxValue. You don't want them to potentially invalidate the existing leftValue and rightValue, like by—let's say—setting maxValue to a value lower than the current rightValue.Pretty straight forward so far.
What however do you do in case of ensuring proper KVO accessibility/compliency? You simply can't assure that KVO sets the properties in the proper order (that being first setting the min and max limits and then the other values).
I happen to have such an UI element and simply can't figure out hot to get this running without opening the doors for falsely set values.
The custom control is meant to be bound to a model object. If I now create such a model with the values (min: 0.00 max: 42.00 left: 14.00 right: 28.00) I end up with the following setup in the custom control (min: 0.00 max: 42.00 left: 1.00 right: 1.00)
The reason for this is that instead of calling minValue and maxValue first, it calls leftValue and rightValue first, which causes both values to end up with 1.0 (which is the default maxValue). (After which the maxValue is then set to its proper value.)
//[self prepare] gets called by both, [self initWithCoder:] and [self initWithFrame:]
- (void)prepare
{
minValue = 0.0;
maxValue = 1.0;
leftValue = 0.0;
rightValue = 1.0;
}
- (void)setMinValue:(double)aMinValue
{
if (aMinValue < maxValue && aMinValue < leftValue) {
minValue = aMinValue;
[self propagateValue:[NSNumber numberWithDouble:minValue] forBinding:@"minValue"];
[self setNeedsDisplay:YES];
}
}
- (void)setMaxValue:(double)aMaxValue
{
if (aMaxValue > minValue && aMaxValue > r开发者_运维问答ightValue) {
maxValue = aMaxValue;
[self propagateValue:[NSNumber numberWithDouble:maxValue] forBinding:@"maxValue"];
[self setNeedsDisplay:YES];
}
}
- (void)setLeftValue:(double)aLeftValue
{
double newValue = leftValue;
if (aLeftValue < minValue) {
newValue = minValue;
} else if (aLeftValue > rightValue) {
newValue = rightValue;
} else {
newValue = aLeftValue;
}
if (newValue != leftValue) {
leftValue = newValue;
[self propagateValue:[NSNumber numberWithDouble:leftValue] forBinding:@"leftValue"];
[self setNeedsDisplay:YES];
}
}
- (void)setRightValue:(double)aRightValue
{
double newValue = leftValue;
if (aRightValue > maxValue) {
newValue = maxValue;
} else if (aRightValue < leftValue) {
newValue = leftValue;
} else {
newValue = aRightValue;
}
if (newValue != rightValue) {
rightValue = newValue;
[self propagateValue:[NSNumber numberWithDouble:rightValue] forBinding:@"rightValue"];
[self setNeedsDisplay:YES];
}
It's times like these when you wish Apple had opened up the source of Cocoa. Or at least some of the controls for deeper inspection.
How would you have implemented such a control?
Or more general: What's the best practice for implementing a class that does have cross-dependent properties while complying to KVO?
Wow. This is a tricky problem. As you've already identified, there's nothing you can do to ensure that KVO messages are sent in any particular order.
Here's one thought: in your setLeftValue:
and setRightValue:
accessors, instead of trimming the value, why not just store the value that's passed in, and then trim the value on access.
For example:
- (void)setLeftValue:(double)value {
leftValue = value;
[self propagateValue:[NSNumber numberWithDouble:self.leftValue] forBinding:@"leftValue"]; // Note the use of the accessor here
[self setNeedsDisplay:YES];
}
- (double)leftValue {
return MAX(self.minValue, MIN(self.maxValue, leftValue));
}
This way, once minValue
and maxValue
are set, leftValue
and rightValue
will represent the correct values. And, by using custom accessors, you can ensure that your constraint that minValue <= leftValue/rightValue <= maxValue
is preserved.
Also, for the code you're writing, the MIN
and MAX
preprocessor macros will save you a lot of effort.
Not exactly sure I understand what your trying to do but your problem has nothing to do with KVO.
The first time that setLeftValue:
is called after prepare to set it to 14.0, you see the following with values of variables in parethesis:
- (void)setLeftValue:(double)aLeftValue(14.0)
{
double newValue = leftValue(0.0);
if (aLeftValue(14.0) < minValue(0.0)) {
newValue = minValue(0.0);
} else if (aLeftValue(14.0) > rightValue(1.0)) { // always evaluates true for all aleftValue>1
newValue = rightValue(1.0); // now newValue==1.0
} else {
newValue = aLeftValue(14.0);
}
if (newValue(1.0) != leftValue(0.0)) {
leftValue = newValue(1.0); // now leftvalue==1.0
[self propagateValue:[NSNumber numberWithDouble:leftValue] forBinding:@"leftValue"];
[self setNeedsDisplay:YES];
}
}
For all aLeftvalues>1
, this code will always set leftValue==1.0
.
Since you never call either of the minValue and maxValue setters, they have no effect on this code.
精彩评论