UIView bringSubviewToFront: does *not* bring view to front

Go To StackoverFlow.com


I am implementing a simple iOS solitaire game that allows the user to drag the cards around in the usual way. The cards are represented with the UIView subclass CardView. All the card view's are siblings which are subviews of SolitaireView. The following snippet tries to "bring a card to the front" so that it is above all the other views as it is being dragged:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   UITouch *touch = [touches anyObject];
   if (touch.view.tag == CARD_TAG) {
      CardView *cardView = (CardView*) touch.view;
      [self bringSubviewToFront:cardView];

Unfortunately, the card's z-order remains unchanged during the drag. In the images below, I am dragging the King. Notice how it is correctly on top the Nine in the left image, but is incorrectly under the Two (under the entire stack actually) in the right image:

enter image description here enter image description here

I also tried alter the layer.zPosition property as well to no avail. How can I bring the card view to the front during the drag? I am mystified.

2012-04-04 20:57
by wcochran
I discovered that bringSubviewToFront: triggers the view's 'needsLayout' flag to be set. My layoutSubviews method was also calling bringSubviewToFront: thus reverting all the view z-orders back to their initial state - wcochran 2012-04-04 22:13
Then whats the solution for this? I am also having same kind of requirements, where from list of cards, if i select any of the middle order card, it should come up, then come to the front. but my animation is not working because of bringSubviewToFront: metho - Mrunal 2013-03-21 14:25
If you stick with using UIView subclasses then you need to mark the card being dragged so that layoutSubviews can place it on top. I actually switched to using CALayers as mentioned below - wcochran 2013-03-22 15:11
Thanks for your reply. Can you please place the code for that - Mrunal 2013-03-22 15:39
A code snippet is listed below - wcochran 2013-03-23 18:29
@Mrunal Source code now at https://github.com/wcochran/solitaire-iO - wcochran 2017-07-01 14:31


Confirmed. bringSubviewToFront: causes layoutSubview to be invoked. Since my version of layoutSubviews sets the z-orders on all the views, this was undoing the z-order I was setting in the touchesBegan:withEvent code above. Apple should mention this side effect in the bringSubviewToFront documentation.

Instead of using a UIView subclass, I created a CALayer subclass named CardLayer. I handle the touch in my KlondikeView subclass as listed below. topZPosition is an instance var that tracks the highest zPosition of all cards. Note that modifying the zPosition is usually animated -- I turn this off in the code below:

-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
   UITouch *touch = [touches anyObject];
   CGPoint touchPoint = [touch locationInView:self];
   CGPoint hitTestPoint = [self.layer convertPoint:touchPoint
   CALayer *layer = [self.layer hitTest:hitTestPoint];

   if (layer == nil) return;

   if ([layer.name isEqual:@"card"]) {
     CardLayer *cardLayer = (CardLayer*) layer;
     Card *card = cardLayer.card;

     if ([self.solitaire isCardFaceUp:card]) {
        [CATransaction begin]; // disable animation of z change                                                                  
        [CATransaction setValue:(id)kCFBooleanTrue
        cardLayer.zPosition = topZPosition++; // bring to highest z

        // ... if card fan, bring whole fan to top

        [CATransaction commit];
     // ...                                                                                                             

2012-04-06 03:10
by wcochran
I think using UIView's for playing cards may have been a bad design choice -- I don't think they really were intended for this. I think I'll step back and use CALayer's instead - wcochran 2012-04-06 18:07