Using UIViewAnimationTransitionFlipFromRight to flip over a UIButton when pressed and show a new button on "the other side"

StackOverflow https://stackoverflow.com/questions/15212238

Question

EDITED For MOXY.

I have an array of buttons with an image on top and array of buttons whose views are background colors. When i click on a button i want to retrieve that button index and use that index to also get the appropriate background color of the other view..and use that information to flip the button(with the image) and "turn it over" to reveal the background color.

The make up of my screen is, ViewControllersView->TileView->(array of buttons).

 -(void)viewDidAppear:(BOOL)animated{
 _viewContents = [model getRequestedView];  //this retrieves an array of UIButtons(24 
  //of them) whose frames and background colors have already been initialized to fit
   //within the TileView.
 _viewFrontImageContents =[model getRequestedViewFrontCardImagesWithFrame:_viewContents];

for(int screenViewIndex = 0; screenViewIndex < [_viewContents count]; screenViewIndex++){
    [TileView addSubview:[_viewFrontImageContents objectAtIndex:screenViewIndex]];
    [[_viewFrontImageContents objectAtIndex:screenViewIndex] addTarget:self action:@selector(frontOfCardPushed:) forControlEvents:UIControlEventTouchUpInside];
    //[TileView addSubview:[_viewContents objectAtIndex:screenViewIndex]];
    //[[_viewContents objectAtIndex:screenViewIndex] addTarget:self action:@selector(frontOfCardPushed:) forControlEvents:UIControlEventTouchUpInside];
}

}

//getRequestedViewFrontCardImagesWithFrame method

-(NSMutableArray *)getRequestedViewFrontCardImagesWithFrame:(NSArray *)views{
        NSMutableArray *cards = [[NSMutableArray alloc] init];
        UIImage *frontOfCardImage = [UIImage imageNamed:@"Back_of_Tile"];
        UIButton *frontOfCardView;

        for(int viewIndex = 0; viewIndex < [views count]; viewIndex++){
            frontOfCardView = [[UIButton alloc] initWithFrame:[[views objectAtIndex:viewIndex] frame]];
            frontOfCardView.backgroundColor = [UIColor colorWithPatternImage:frontOfCardImage];
  //setting the tag of each frontCard to match the view index to keep them aligned.
            frontOfCardView.tag = viewIndex;
            [cards addObject:frontOfCardView];

            if(viewIndex == 0){
              NSLog(@"first frontcard frame %@, first _viewContentsFrame %@", [cards objectAtIndex:0], [views objectAtIndex:0]);
    }
        }
        return cards;
    }

Above I am adding UIButtons with an image on top to represent the "front of a playing card". The indices of said buttons matches exactly the indeces of the _viewContents array.I set up each button to match the frame(size and location) of the buttons that have been initialized already.

-(IBAction)frontOfCardPushed:(id)sender{
int containerViewIndex = -1;
UIButton *pressedButton = (UIButton *)sender;
NSLog(@"Button Index: %i", pressedButton.tag);
containerViewIndex = pressedButton.tag;
UIView *container = [TileView viewWithTag:containerViewIndex];
UIView *viewToShow = [_viewContents objectAtIndex:containerViewIndex];
NSLog(@"container: %@, viewToShow: %@", container, viewToShow);
//viewToShow.frame = container.frame;
//NSLog(@"container: %@, viewToShow: %@", container, viewToShow);

[UIView transitionWithView:container duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight
                animations:^{
                    [sender removeFromSuperview];
                    [container addSubview:viewToShow];
                }
                completion:nil];

what I want to do is use a transition - namely 'UIViewAnimationTransitionFlipFromRight' to"flip the card" when pressed to reveal the contents of the associated _viewContents array. I also want to be able for the user to flip the card back over to reveal the image of the frontCardButton. This piece of code above just focuses on the first flip of the card to avoid a wall of code.

I am seeing the appropriate background colors - but the animation itself is not working.

What am i doing wrong and what do I need to add/remove to this piece of code to make it work?

  • EDIT* As noted below to Moxy, take a look at the output of whats in the getRequestedViewFrontCardImagesWithFrame method. Look at the output of the first card and then look at the output of the container view in the frontCardPushed: method after requesting the view with the appropriate tag - they do no match. I am printing out the button index so that you can see I am hitting the first TileView subview. The tag of the _viewContents view is a tag set the represents a color and has nothing to do with the TileView tag that I assign in the getRequestedViewFrontCardImagesWithFrame method. It is puzzling however that right after i assign a tag to the first button when i print it out, a tag isnt listed.

OUTPUT:

//why doesnt the first frontcard frame list a tag? I had just assigned it one?

first frontcard frame UIButton: 0x1c53f020; frame = (6 5; 145 145); opaque = NO; layer = CALayer: 0x1c534ac0, first _viewContentsFrame UIButton: 0x1d045220; frame = (6 5; 145 145); opaque = NO; tag = 3; layer = CALayer: 0x1d04535

Button Index: 0
container: UIView: 0x1c53a400; frame = (51 83; 922 614); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = NSArray: 0x1d05afe0>; layer = CALayer: 0x1c53a460, viewToShow: UIButton: 0x1d045220; frame = (6 5; 145 145); opaque = NO; tag = 3; layer = CALayer: 0x1d045350

Was it helpful?

Solution 2

You could make it work by using a container view to hold the buttons.

-(void)viewDidAppear:(BOOL)animated
{
    _viewContents = [model getRequestedView];  //this retrieves an array of UIButtons(24 of them) whose frames and background colors have already been initialized to fit within the TileView.

    for(int i = 0; i < [_viewContents count]; i++){
        UIView *container = [[UIView alloc] initWithFrame:[[_viewContents objectAtIndex:screenViewIndex] frame]]; 
        container.tag = i;      
        UIButton *frontOfCardView = [[UIButton alloc] initWithFrame:container.bounds];
        frontOfCardView.backgroundColor = [UIColor colorWithPatternImage:_frontOfCardImage];
        frontOfCardView.tag = i;
        [container addSubview:frontOfCardView]; 
        [frontOfCardView addTarget:self action:@selector(frontOfCardPushed:) forControlEvents:UIControlEventTouchUpInside];
        [tileView addSubview:container];
    }
}

-(IBAction)frontOfCardPushed:(UIButton *)sender
{
    UIView *container = [tileView viewWithTag:sender.tag];
    UIView *viewToShow = [_viewContents objectAtIndex:index];
    viewToShow.frame = sender.frame;
    [UIView transitionWithView:container
                      duration:0.5
                       options:UIViewAnimationOptionTransitionFlipFromRight
                    animations:^{
                      [sender removeFromSuperView];
                      [container addSubview:viewToShow];
                    }
                    completion:nil];
}

I haven't tested it but it should work. Anyway, this is not very clean/pretty code. But, I understand it's only for learning purpose.

Let me know if it still doesn't work.

EDIT :

I made a simple project with a single view controller as an example. I think that the way you keep track of all your views/buttons is useless. In this case the model is only a background color and all you want to do is to reveal/hide it, so once you assign the color to a button, all it has to know is to switch states, so you don't even need tags.(In other cases the controller would need to identify which button that received an event in order to ask the model what content it needs)

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) NSArray *colors;
@property (nonatomic, strong) UIView *tileView;
@end

@implementation ViewController

#pragma mark -
#pragma Getters and setters

// I'm assuming the tileView is just a simple view to wrap all the buttons
// PS : propertys should always start with a lowercase letter
-(UIView *)tileView
{
    if (!_tileView) {
        _tileView = [[UIView alloc] initWithFrame:self.view.bounds];
        _tileView.backgroundColor = [UIColor greenColor];
    }
    return _tileView;
}

// The model: just an array of colors, you can generate it or use other complicated stuff
-(NSArray *)colors
{
    if (!_colors) {
        _colors = @[[UIColor blackColor], [UIColor redColor], [UIColor grayColor], [UIColor yellowColor],
                    [UIColor whiteColor], [UIColor blueColor], [UIColor orangeColor], [UIColor darkGrayColor],
                    [UIColor lightGrayColor], [UIColor cyanColor], [UIColor brownColor], [UIColor magentaColor],
                    [UIColor blackColor], [UIColor redColor], [UIColor grayColor], [UIColor yellowColor],
                    [UIColor whiteColor], [UIColor blueColor], [UIColor orangeColor], [UIColor darkGrayColor],
                    [UIColor lightGrayColor], [UIColor cyanColor], [UIColor brownColor], [UIColor magentaColor]];
    }
    return _colors;
}

#pragma mark -
#pragma mark Helper methods

#define ROWS 6
#define COLUMNS 4
#define CARD_HEIGHT 60.0f
#define CARD_WIDTH 40.0f

// Gets the width that each card can use based on the chose number of columns
-(CGFloat)horizontalStep
{
    return self.tileView.frame.size.width / COLUMNS;
}

// Gets the height that each card can use based on the chose number of columns
-(CGFloat)verticalStep
{
    return self.tileView.frame.size.height / ROWS;
}

// Calculates the center for the card frame (return (0,0) if the index is out of bounds)
-(CGPoint)centerForCardAtIndex:(NSUInteger)index
{
    CGFloat x = 0.0f, y = 0.0f;
    if (index < [self.colors count]) {
        x = [self horizontalStep] * 0.5f + (index % COLUMNS) * [self horizontalStep];
        y = [self verticalStep] * 0.5f + (index / COLUMNS) * [self verticalStep];
    }
    return CGPointMake(x, y);
}

// Gets the frame for the card at index
-(CGRect)frameForCardAtIndex:(NSUInteger)index
{
    CGPoint center = [self centerForCardAtIndex:index];
    CGPoint origin = CGPointMake(center.x - CARD_WIDTH * 0.5f, center.y - CARD_HEIGHT * 0.5f);
    return CGRectMake(origin.x, origin.y, CARD_WIDTH, CARD_HEIGHT);
}

-(void)layoutCards
{
    // Add tileView to the controller's view
    [self.view addSubview:self.tileView];
    // Create the card buttons and add them to the tile view
    for (int i = 0; i < [self.colors count]; i++) {
        UIButton *card = [UIButton buttonWithType:UIButtonTypeCustom];
        card.frame = [self frameForCardAtIndex:i];

        // The only use of the model IN THIS CASE
        card.backgroundColor = self.colors[i];
        [card setImage:[UIImage imageNamed:@"back_of_tile.png"]
              forState:UIControlStateNormal];

        [card addTarget:self
                 action:@selector(cardPressed:)
       forControlEvents:UIControlEventTouchUpInside];

        [self.tileView addSubview:card];
    }
}

-(void)cardPressed:(UIButton *)card
{
    [UIView transitionWithView:card
                      duration:0.5
                       options:UIViewAnimationOptionTransitionFlipFromRight
                    animations:^{
                        card.selected = !card.selected;
                        if (!card.selected) {
                            [card setImage:[UIImage imageNamed:@"back_of_tile.png"]
                                  forState:UIControlStateNormal];
                        } else {
                            [card setImage:nil
                                  forState:UIControlStateNormal];
                        }
                    }
                    completion:nil];
}


- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self layoutCards];
}

OTHER TIPS

An easier way to do this would be to use [UIView transitionWithView]:

[UIView transitionWithView:self.flipButton
                  duration:0.8
                   options:displayingFirstButton ? UIViewAnimationOptionTransitionFlipFromLeft : UIViewAnimationOptionTransitionFlipFromRight
                animations:^{
                  //change image for button
                }
                completion:^(BOOL finished) {

                }];
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top