April 27, 2013

Changed Graphics Cards - Saved Money ?

I switched graphic cards in my home machine from a 8800 GTS to a GTX 660. The difference was an astounding 70W (measured) during idle! With current energy prices, the new card pays for itself in about ... 5 years.  Which is about the usage time of the old card.

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 ?

April 25, 2013

UIAppearance: Magic == Pain

This shit is so obscure, that I needed to a lot of research to get a coherent view of what's happening in my head.

The problem with UIAppearance geometry values

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.

The clever 90% Solution

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.

The painful 100% Solution

The only good solution I have come up so far is this: Setup your view hierarchy like you just don't care, possibly in -[UIViewController viewDidLoad]. The real work has to be done delayed. For that you have to override your subclass -[UIView didMoveToWindow] method and call a custom method on the UIViewController. Clumsy, but magic always comes with a price.

Rule #3: Geometry UI_APPEARANCE_SELECTORs need special attention

April 24, 2013

-[UIButton tintColor] is just weird and non-conforming - or class clusters don't mix well with UIAppearance

I figured, that I wanted to use UIAppearance for my code, so that I can easily "skin" my iOS applications. What I wasn't really clear about is how to properly handle default values, because like most recent Apple code, it's just magic. I hate magic.

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.


Addendum: I should add to my examples, that values from UIApperance only show up in the UIView when the window has been made visible. So the appearance setting should be done ahead of the NIB loading and the reading of values can only be done after [window makeKeyAndVisible]. More obscure magic.

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.


Another Addendum: The problem is really with UIAppearance instead of UIButton:

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>
@end
and

"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.

April 10, 2013

Neuer Slogan für die SPD

Mal was Politisches :)

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.

Categories