Changed Graphics Cards - Saved Money ?
In terms of the mathematics, was this now the optimal time to change cards, given the pricepoint and the wattage and availablity of this card at this point in time ?
In terms of the mathematics, was this now the optimal time to change cards, given the pricepoint and the wattage and availablity of this card at this point in time ?
Let's assume that I have a property padding that defines the size of an inner border of my UIView subclass. It's merely a decorative element and has a default value of 2 pixels on each side.
-(void) initWithCoder:(NSCoder *) coder
{
self = [super initWithCoder:coder];
if( self)
self->_padding = UIEdgeInsetsMake( 2, 2, 2, 2);
return( self);
}
// and the same for initWithFrame:
I want this to be skinnable so I make it an UI_APPEARANCE_SELECTOR.
@property(nonatomic) UIEdgeInsets padding UI_APPEARANCE_SELECTOR;
In my UIView subclass I want to host a contentView of some random size (code not shown). How do I calculate the frame.size of my view ? I get the frame of the contentView and add the padding. Simple.
UIEdgeInsetsInsetRect insets;
insets = [self padding];
insets.top = - insets.top;
insets.left = - insets.left;
insets.bottom = - insets.bottom;
insets.right = - insets.right;
frame = UIEdgeInsetsInsetRect( [_contentView frame],
insets);
Will this work ? It depends. It depends on WHEN I call it.
Here's where the research comes in. This is the order in which a typical iOS program calls some of the key classes instance methods. This is not a stack trace, just the chronological order. The -[UIView setPadding:] call is the UIAppearance setting the value in the UIView:
-[UIWindow initWithFrame:] -[UIViewController initWithNibName:] -[UIWindow setRootViewController:] -[UIWindow makeKeyAndVisible] -[UIView initWithCoder:] -[UIView awakeFromNib] -[UIViewController viewDidLoad] -[UIViewController viewWillAppear:] -[UIView willMoveToWindow:] +[UIView requiresConstraintBasedLayout] -[UIView setPadding:] -[UIView didMoveToWindow] -[UIView layoutSubviews] -[UIView drawRect:] -[UIViewController viewDidAppear:]It is important to notice, that the UIAppearance value is available only during and after -[UIView didMoveToWindow]. It is NOT available during -[UIViewController viewDidLoad] or -[UIViewController viewWillAppear:]. And -[UIViewController viewDidAppear:] is obviously too late. As the constraint code is running earlier, auto layout code would not help either.
Rule #1: Do not access UI_APPEARANCE_SELECTOR properties if -[UIView window] returns nil
Now you may think: "hey, do the code in -[UIView layoutSubviews]". But as the name indicates, that method should layout the subviews of my UIView and not change its own frame. That should have been set during the superview's layoutSubviews method. Otherwise the superview could be surprised and misconfigured. In the end, it's really the job of the UIViewController to set my UIView's frame.
But the UIViewController doesn't have a chance anymore to do this.
How can this be fixed ? Well it would be nice, if the UIView subclass offered a method like +frameForContentRect:, so the view's frame can be calculated in advance, during -[UIViewController viewDidLoad] given the frame of the future contentView:
+ (CGRect) frameForContentRect:(CGRect) rect
{
id <UIAppearance> appearance;
UIEdgeInsetsInsetRect insets;
appearance = [self appearance];
insets = [appearance padding];
insets.top = - insets.top;
insets.left = - insets.left;
insets.bottom = - insets.bottom;
insets.right = - insets.right;
return( UIEdgeInsetsInsetRect( rect, insets));
}
Nice, but it doesn't work really well, if no one is setting the UIAppearance value, and we fall back to the default value of the instance.
Solution ? Don't set the default value in the instance but instead preset the UIAppearance with it at +load time and delete the instance default value code from initWithCoder: (and initWithFrame:)
+ (void) load
{
[[self appearance] setPadding:UIEdgeInsetsMake( 2.0, 2.0, 2.0, 2.0)]);
}
Nicey, but does it solve the problem ?
Not always, because the value of padding could also be set by user code on an individual instance. And then the UIAppearance value won't be used. Obviously there is no chance for knowing that in the custom +frameForContentRect: method, since it's a class method.
Rule #2: Set UI_APPEARANCE_SELECTORs default values in +load
I think using +load to set default UIAppearance values is still a good and rather clean method, but for the problem at hand it's not the whole solution. It's pretty clean, because you can be sure that the default is set earlier than -[id <UIApplicationDelegate> application:didFinishLaunchingWithOptions:], which one would expect the application coder to set his UIAppearance values.
Rule #3: Geometry UI_APPEARANCE_SELECTORs need special attention
To test the best way to handle default values for my UIViews, I wanted to test UIAppearance first with the most simple and minimal code possible and go from there. I didn't even make it...
Step 1: Create a new iOS application with a single view
Step 2: Enter the following lines in the app delegate:
- (BOOL) application:(UIApplication *) application
didFinishLaunchingWithOptions:(NSDictionary *) launchOptions
{
UIButton *button;
[[UIButton appearance] setTintColor:[UIColor orangeColor]];
button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
NSLog( @"%@", [button tintColor]);
}
Step 3: Run and observe console. Notice that it doesn't work, tintColor returns nil. Which is surprising.
The documentation says:
tintColor The tint color for the button. @property(nonatomic, retain) UIColor *tintColor Discussion The default value is nil. This property is not valid for all button types.Wasn't Apple's documentation somewhat better in previous times ? Why not write for what button types tintColor is valid ? Now came the most surprising part. I wrote a little loop to figure out, when tintColor is valid.
UIButton *button;
NSUInteger i;
for( i = 0; i < 10; i++)
{
button = [UIButton buttonWithType:i];
NSParameterAssert( button);
[button setTintColor:[UIColor greenColor]];
NSLog( @"%ld: %@", (long) i, [button tintColor]);
}
And as it turns out
2013-04-24 15:57:11.496 ApperanceTest[2732:c07] 0: (null) 2013-04-24 15:57:11.498 ApperanceTest[2732:c07] 1: UIDeviceRGBColorSpace 0 1 0 1 2013-04-24 15:57:11.499 ApperanceTest[2732:c07] 2: (null) 2013-04-24 15:57:11.499 ApperanceTest[2732:c07] 3: (null) 2013-04-24 15:57:11.500 ApperanceTest[2732:c07] 4: (null) 2013-04-24 15:57:11.500 ApperanceTest[2732:c07] 5: (null) 2013-04-24 15:57:11.500 ApperanceTest[2732:c07] 6: (null) 2013-04-24 15:57:11.501 ApperanceTest[2732:c07] 7: (null) 2013-04-24 15:57:11.501 ApperanceTest[2732:c07] 8: (null) 2013-04-24 15:57:11.502 ApperanceTest[2732:c07] 9: (null)the only valid value is ironically 1 which is UIButtonTypeRoundedRect, the value I used in the first place.
So in the end although tintColor is advertised as being modifiable through UIAppearance in the header @property(nonatomic,retain) UIColor *tintColor NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR; // default is nil. only valid for some button types, it really seems not to be the case.
I would on a hunch assume, that's because UIButton could be a class cluster and the subclasses implement their own tintColor accessors and the whole magic breaks down.
As the button is indeed an instance of a class cluster subclass called UIRoundedRectButton, I tried [[NSClassFromString(@"UIRoundedRectButton") appearance] setTintColor:[UIColor greenColor]]; and then it worked as I'd expected it in the first place.
Create an empty single view IOS project and create a UIView subclass:
"MulleView.h"
#import <UIKit/UIKit.h> @interface _MulleView : UIView < UIAppearanceContainer>: @end @interface MulleView : _MulleView < UIAppearanceContainer> @endand
"MulleView.m"
#import "MulleView.h"
@implementation _MulleView
+ (void) load
{
[[self appearance] setXColor:[UIColor blueColor]];
}
- (void) setXColor:(UIColor *) color
{
NSLog( @"%s", __PRETTY_FUNCTION__);
}
@end
@implementation MulleView
- (void) willMoveToWindow:(UIWindow *)newWindow
{
NSLog( @"%s", __PRETTY_FUNCTION__);
}
- (void) setXColor:(UIColor *) color
{
NSLog( @"%s", __PRETTY_FUNCTION__);
}
@end
Then set the class of the root view of your xib to MulleView instead of UIView and you will see this in the output when running:
-[MulleView willMoveToWindow:] -[_MulleView setXColor:]understandable but wrong for class clusters.
Das neue Wahlkampfmotto der SPD "Das Wir entscheidet" steht ja unter keinem guten Stern, daher habe ich hier mal schnell einen schönen Ersatz geschaffen. Viel ehrlicher und nicht so verdruckst, als ob man keine Ahnung von nix hat.