Sunday, September 18, 2011

UIViewAutoresizing Has Limits

If you're going to try to resize stuff seriously -- all the way down, then all the way up -- UIViewAutoresizing sucks. Why? All the information it uses comes from the current state. Over resizes to smaller sizes it will lose precision. This means that a "flexible sides" subview that was 180 pixels from the left side could end up at 18 pixels from the left side. So you'll need to store your subviews' sizes. You get something cheesy like this:

#pragma mark All the storing position stuff since UIViewAutoSize sucks
-(NSMutableDictionary *)positions {
    if (!positions)
        self.positions = [NSMutableDictionary dictionary];
    return positions;
}

- (void) repositionElements {
    for (NSValue *key in [self.positions allKeys]) {
        [self reposition:[key nonretainedObjectValue]];
    }

}

- (void) storePositionsForElements {
    for (UIView *view in self.subviews) {
        if (CGRectIsEmpty(view.frame))
            return;
        [self storeFirstPosition:view];
    }
}


-(void)storeFirstPosition:(UIView *)element {
    NSValue *key = [NSValue valueWithNonretainedObject:element];
    [self.positions setObject:[NSValue valueWithCGRect:element.frame] forKey:key];
}
     
 - (void)reposition:(UIView *)element {
     NSValue *key = [NSValue valueWithNonretainedObject:element];

     CGRect firstFrame = ((NSValue*)[self.positions objectForKey:key]).CGRectValue;
     float newX = firstFrame.origin.x / firstBounds.size.width * self.bounds.size.width;
     float newY = firstFrame.origin.y / firstBounds.size.height * self.bounds.size.height;
     float newWidth = firstFrame.size.width / firstBounds.size.width * self.bounds.size.width;
     float newHeight = firstFrame.size.height / firstBounds.size.height * self.bounds.size.height;
     element.frame = CGRectMake(newX, newY, newWidth, newHeight);   
 }

This would allow you to store everything in an NSMutableArray called positions (make sure it's not nil, for God's sake). You would call storePositionsForElements in your initWithCoder (so it stores the NIB's positions), and then call repositionElements from your setFrame override.

If categories let you store stuff, I would monkey patch UIView and get a much better solution. 

Friday, September 16, 2011

Lock Aspect Ratio to Max

This is pretty much exactly how UIImageView works when you use "Aspect Fill." If you use this in the enclosing view's setFrame, you can let the UIImageView freewheel (scale to fit), and resize other things according to your new size:

-(void)setFrame:(CGRect)frameIn {
    if (!ignoreAspectRatio) {
        float aspectRatio = self.frame.size.width / self.frame.size.height;
        frameIn.size.width = MIN(frameIn.size.width, frameIn.size.height * aspectRatio);
        frameIn.size.height = frameIn.size.width/aspectRatio;
    }
    [super setFrame:frameIn];
}

This kind of programming is not my strong suit, but with a few minutes goofing around in Excel, it was easy to figure out. And it works.

Monday, September 5, 2011

Parameters into iOS Apps, XCode 4.x

I needed to get arguments and version numbers and so forth into an iOS app.
The arguments come in via the Scheme. You can have one scheme per target, or one scheme per multiple targets. I'm using arguments in the format paramOne=35.5 paramTwo=release


Then you can pick up the arguments like this:

Version numbers, differently, come in via the target. You get a "version" number and a "build" number.


Then you can pick up the build and version with something like this


Yet another way, from my colleague Jose at Thingee: