Gold Challenge Solution: Overriding Autorotation


#1

I added an outlet for the new button called topLeftButton:

Then added a CGPoint property for the starting point of the new button:

Inside the viewDidLoad method override, I set the starting point property of the of the topLeftButton to be its center position when the app loads:

Added an override to the willAnimateRotationToInterfaceOrientation:duration method using point offsets for the ending point of the button:

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)x duration:(NSTimeInterval)duration { CGRect bounds = [[self view] bounds]; if (UIInterfaceOrientationIsPortrait(x)) { [topLeftButton setCenter:startingPoint]; } else { [topLeftButton setCenter:CGPointMake(bounds.size.width - 56, bounds.size.height - 96.5)]; } }
Before:

After:


#2

An alternative solution (without resorting to magic numbers. A a rule of thumb, magic numbers which should be avoided since they are sensitive to the physical placement of the button):

Declare instance variables, x and y offsets:

CGFloat xMarginOffset;
 CGFloat yMarginOffset;

Then in the viewDidLoad method, calculate the offsets:

// x margin offset = the button x origin in the view's coordinate space 
 xMarginOffset = button.frame.origin.x;
    
// y margin offset = the view's height minus the origin of the button
yMarginOffset = self.view.bounds.size.height - button.frame.origin.y;

Finally to animate the rotation:

- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration {
    
    CGRect frame = button.frame;
    CGFloat width = button.bounds.size.width;
    
    // The y origin of the button is always the max height minus the y margin offset
    frame.origin.y = self.view.bounds.size.height - yMarginOffset;
    
    if (orientation == UIInterfaceOrientationPortrait) {
        // The x origin of the button in portrait mode is alawys the x margin offset
        frame.origin.x = xMarginOffset;
        [button setFrame: frame];
    } else if (UIInterfaceOrientationIsLandscape(orientation)) {
        // The x origin of the button in landscape mode is always the max width - button width - x margin offset
        frame.origin.x = self.view.bounds.size.width - width - xMarginOffset;
        [button setFrame: frame];
    }
    
}

#3

@stevet Good solution. I was looking for a way to do this without resorting to magic numbers but couldn’t think of way to implement it.


#4

I wanted to add the button programmatically so that I would have the starting point already stored. Could someone please explain why this piece of code did not produce any button on the view?

//
//  HeavyViewController.m
//  HeavyRotation
//
//  Created by Benjamin Waye on 4/26/12.
//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        CGRect frame = CGRectMake(10, 200, 30, 30);
        button = [[UIButton alloc] initWithFrame:frame];
        [[self view] addSubview:button];
    }
    return self;
}

And now that I decided to do it through interface builder I stumbled upon the following problem>

-(void) viewDidLoad
{
    [super viewDidLoad];
    
    startLoc = [button center];
-(void) willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)x
                                                    duration:(NSTimeInterval)duration
{
    if(UIInterfaceOrientationIsLandscape(x))
       {
           [button setCenter:CGPointMake(self.view.bounds.size.width - button.bounds.size.width, self.view.bounds.size.height/2)];
       }
    else {
        [button setCenter];
    }
}

This give me the following results:

and Landscape mode

The button doesn’t go all the way to the right nevermind not being in the center :stuck_out_tongue:, does anyone know why?


#5

For this challenge, I added instance variables to HeavyViewController to hold the starting margin and width of the new button. Like @stevet, I calculated them in viewDidLoad.

    // Get the Gold Challenge button's margin and width
    goldButtonMargin = [goldButton frame].origin.x;
    goldButtonWidth = [goldButton frame].size.width;

I’m a big fan of the ternary operator, so I use it whenever possible. So I used it in my willAnimateRotationToInterfaceOrientation:duration. I think it improves the code’s readability.

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation 
                                         duration:(NSTimeInterval)duration
{
    CGRect bounds = [[self view] bounds];
    // Calculate the X center of the button depending on orientation
    float buttonX = UIInterfaceOrientationIsPortrait(orientation) ?
                     goldButtonMargin + goldButtonWidth / 2.0 :
                     bounds.size.width - goldButtonMargin - goldButtonWidth / 2.0;
    // The Y center is always halfway from the top
    float buttonY = CGRectGetMidY(bounds);
    // Set the center of the button
    [goldButton setCenter:CGPointMake(buttonX, buttonY)];
}

#6

during the rotation, the button width becomes the button height and vice-versa … and as your button is not square, it is farther from the frame of the window in landscape than in portrait.


#7

Is it bad to go with this simple approach? I created a stepper instead of a button (just to try something new) in the XIB. In my view controller header file, I created an outlet to the stepper pointer called *step. Then, in the implementation file for the view controller, I just did this:

-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { CGRect bounds = [[self view] bounds]; if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) { [step setCenter:CGPointMake(bounds.size.width - (bounds.size.width * .25), bounds.size.height - (bounds.size.height * .12))]; } else { [step setCenter:CGPointMake(bounds.size.width - (bounds.size.width * .6), bounds.size.height - (bounds.size.height * .08))]; } }

I did have to toy with the percentage reductions of width and height, but I was guessing that this type of percentage approach would have better success on the various screen resolutions. Any thoughts?


#8

HeavyViewController.h

#import <UIKit/UIKit.h>

@interface HeavyViewController : UIViewController
{
    IBOutlet UIButton *button;
    
    int xOffset, yOffset;
}

@end

HeavyViewController.m

#import "HeavyViewController.h"

@interface HeavyViewController ()

@end

@implementation HeavyViewController

-(void)viewDidLoad
{
    xOffset = button.frame.origin.x + button.frame.size.width/2;
    yOffset = self.view.bounds.size.height - (button.frame.origin.y + button.bounds.size.height/2);
    
    NSLog(@"height %f offset %i origin %f", self.view.bounds.size.height, yOffset, button.frame.origin.y + button.frame.size.height/2);
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)x
{
    return (x == UIInterfaceOrientationPortrait)
    ||  UIInterfaceOrientationIsLandscape(x);
}

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)x
                                         duration:(NSTimeInterval)duration
{    
    CGRect bounds = [[self view] bounds];

    if (UIInterfaceOrientationIsPortrait(x)) {
        
        [button setCenter:CGPointMake(xOffset,
                                      self.view.bounds.size.height-yOffset)];
    } else {
        
        [button setCenter:CGPointMake(bounds.size.width-xOffset,
                                      self.view.bounds.size.height-yOffset)];
    }
}

@end