2

I have a set of views that represent cards, that should all fit into it's superview:

enter image description here

I want the container view to be aligned near the center of the screen, so I added these constraints for it:

  • Leading space with it's container;
  • Top space with the layout guide (the view controller is inside a navigation controller);
  • Center horizontally in the container;
  • Center vertically in the container.

This way the view that contains all the other small views is somehow pinned, and in landscape mode it gets more tiny so that it's subviews should shrink a lot vertically.

I added on all the subviews inside the view these constraints:

  • Same width;
  • Same height;
  • Leading, trailing, top and bottom space with any neighbor view.

To make it clearer this last step I'll also add this picture:

enter image description here

Now the first problem is that when the iphone simulator gets rotated to landscape, all the views are too much shrunk:

enter image description here

And I understand why: the top and vertical space from neighbors has a standard value, which maybe in landscape mode is too high relatively to the available vertical space, so they're too short. But how do I specify a constraint that takes a space in percentage (e.g.: vertical separation: 5% of the view's width)? The best to do in my case is to resize the subviews in the same way as if they were scaled to fit inside the new subview's bounds. How do I do that?

Another problem is that the app crashes if I rotate it to landscape and then to portait upside down:

2014-03-17 23:40:04.736 Matchismo[10284:60b] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x109226d90 V:|-(NSSpace(20))-[PlayingCardView:0x109224330]   (Names: '|':UIView:0x109226610 )>",
    "<NSLayoutConstraint:0x109226e80 V:[PlayingCardView:0x109224330]-(NSSpace(8))-[PlayingCardView:0x109225080]>",
    "<NSLayoutConstraint:0x109227150 V:[PlayingCardView:0x109225080]-(NSSpace(8))-[PlayingCardView:0x109225700]>",
    "<NSLayoutConstraint:0x1092277e0 V:[PlayingCardView:0x109225d80]-(NSSpace(20))-|   (Names: '|':UIView:0x109226610 )>",
    "<NSLayoutConstraint:0x109227830 PlayingCardView:0x109225d80.height == PlayingCardView:0x109225700.height>",
    "<NSLayoutConstraint:0x109227970 PlayingCardView:0x109225d80.height == PlayingCardView:0x109224330.height>",
    "<NSLayoutConstraint:0x109227ba0 PlayingCardView:0x109225d80.height == PlayingCardView:0x109225080.height>",
    "<NSLayoutConstraint:0x109227ce0 V:[PlayingCardView:0x109225700]-(NSSpace(8))-[PlayingCardView:0x109225d80]>",
    "<NSLayoutConstraint:0x10922a0e0 UIView:0x109228f40.centerY == UIView:0x109226610.centerY + 32>",
    "<NSLayoutConstraint:0x10922a130 V:[_UILayoutGuide:0x109229020]-(14)-[UIView:0x109226610]>",
    "<_UILayoutSupportConstraint:0x109224980 V:[_UILayoutGuide:0x109229020(64)]>",
    "<_UILayoutSupportConstraint:0x109221ff0 V:|-(0)-[_UILayoutGuide:0x109229020]   (Names: '|':UIView:0x109228f40 )>",
    "<NSAutoresizingMaskLayoutConstraint:0x10923eef0 h=--& v=--& V:[UIView:0x109228f40(271)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x109227ce0 V:[PlayingCardView:0x109225700]-(NSSpace(8))-[PlayingCardView:0x109225d80]>

Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
(lldb)  

But I can't see any conflicting constraints.

Solution

Exploiting ReedD's answer, I decided to use a collection view. This is the solution I came around with (in my case I need to use 16 cards in a 4x4 collection view, but the code can be rearranged to work in a generic way):

- (NSInteger) collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 4;
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 4;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell* cell= [collectionView dequeueReusableCellWithReuseIdentifier:@"reuseIdentifier" forIndexPath:indexPath];

    <Initialize the card view and add it to the cell>

    return cell;
}

- (CGSize) collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return CGSizeMake(collectionView.contentSize.width/4.0 -5.0,collectionView.contentSize.height/4.0 -5.0);
}

- (UIEdgeInsets) collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    return UIEdgeInsetsMake(5, 5, 5, 5);
}
Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187

3 Answers3

2

To address your last problem:

When ever you see the following in your crash log -

"<NSAutoresizingMaskLayoutConstraint:0x10923eef0 h=--& v=--& V:[UIView:0x109228f40(271)]>"

-- your first hunch would be to go and write this line for the view having memory address 0x109228f40

self.view.translatesAutoresizingMaskIntoConstraints= NO;
Anindya Sengupta
  • 2,539
  • 2
  • 21
  • 27
1

Have you tried to put a height constraint on the first card and set it to >= 20 (or any minimum value that could be reasonable for you)? Also, are the score and the new game button constrained in any way to the card container? If so, you should try to remove any vertical spacing between them.

Stefano Mondino
  • 1,219
  • 11
  • 15
1

Have you thought of possibly using a UICollectionView? Then with the datasource delegate you could do something to the effect of this:

#pragma mark - UICollectionViewDatasource

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout  *)collectionViewLayout
  sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    if (UIDeviceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
        // Landscape
        return CGSizeMake(50, 100);
    }
    // Portrait
    return CGSizeMake(100, 200);
}
ReedD
  • 997
  • 7
  • 6
  • That's the way of doing it. But please, correct the part where you verify if it's in landscape or portait mode, the correct way to do this is checking the collection view bounds and to properly create the sub-frames. – Ramy Al Zuhouri Mar 24 '14 at 11:40
  • Hmm, perhaps I don't understand why you would like that corrected. Here are two other solutions that use this same method. http://stackoverflow.com/a/13656570/3322075 http://stackoverflow.com/a/22505766/3322075 – ReedD Mar 24 '14 at 16:52
  • I used a slight different implementation, in a way that it works with every view size. This way the code is reusable, because the collection view may have a different size and it could require smaller cells. But this answer has really been enlightening so I'll give you the bounty reward. – Ramy Al Zuhouri Mar 24 '14 at 17:41
  • Awesome, I'm glad it helped get you to a working solution. Perhaps you can share how your solution differed/improved this one. – ReedD Mar 24 '14 at 18:29